仅使用私有构造函数扩展类

inf*_*thi 10 java reflection

问题是:我有一个只有私有构造函数的类(我不能修改它的源代码),我需要扩展它.

因为反射允许我们随时创建这样的类的实例(通过获取构造函数并调用newInstance()),是否有任何方法可以创建此类的扩展版本的实例(我的意思是,无论如何,即使它反对OOP)?

我知道,这是一个不好的做法,但看起来我别无选择:我需要拦截对一个类的一些调用(它是一个单例,它不是一个接口实现,所以动态代理在这里不起作用).

最小的例子(根据要求):

public class Singleton {
static private Singleton instance;

private Singleton() {
}

public static Singleton getFactory() {
    if (instance == null)
        instance = new Singleton();
    return instance;
}

public void doWork(String arg) {
    System.out.println(arg);
}}
Run Code Online (Sandbox Code Playgroud)

我想做的就是构建自己的包装器(就像这个一样)

class Extension extends Singleton {
@Override
public void doWork(String arg) {
    super.doWork("Processed: " + arg);
}}
Run Code Online (Sandbox Code Playgroud)

并使用反射将其注入Factory:

Singleton.class.getField("instance").set(null, new Extension());
Run Code Online (Sandbox Code Playgroud)

但是我没有看到任何构造这样的对象的方法,因为它的超类的构造函数是私有的.问题是"这是否可能".

Ren*_*ink 7

如果可能(但是糟糕的黑客)

  • 你有私有构造函数的类的源代码,或者你可以从字节码重构它
  • 该类由应用程序类加载器加载
  • 你可以修改jvm的类路径

您可以创建与原始类二进制兼容的补丁.

我将在下一节中调用您想要扩展PrivateConstructorClass的类.

  1. 获取源代码PrivateConstructorClass并将其复制到源文件.不得更改包和类名.
  2. PrivateConstructorClassprivate 的构造函数更改为protected.
  3. 重新编译修改后的源文件PrivateConstructorClass.
  4. 将已编译的类文件打包到jar存档中.例如称为"patch.jar"
  5. 创建一个扩展第一个类的类,并根据patch.jar中的类对其进行编译
  6. 更改jvm的类路径,以便patch.jar是类路径中的第一个条目.

现在一些示例代码可以让您检查它是如何工作的:

期待以下文件夹结构

+-- workspace
  +- private
  +- patch
  +- client
Run Code Online (Sandbox Code Playgroud)

PrivateConstructorprivate文件夹中创建类

public class PrivateConstructor {


    private String test;

    private PrivateConstructor(String test){
        this.test = test;
    }

    @Override
    public String toString() {
        return test;
    }
}
Run Code Online (Sandbox Code Playgroud)

private文件夹中打开命令提示符,编译并打包它.

$ javac PrivateConstructor.java
$ jar cvf private.jar PrivateConstructor.class
Run Code Online (Sandbox Code Playgroud)

现在在文件patch夹中创建补丁文件:

    public class PrivateConstructor {


    private String test;

    protected PrivateConstructor(String test){
        this.test = test;
    }

    @Override
    public String toString() {
        return test;
    }
}
Run Code Online (Sandbox Code Playgroud)

编译并打包它

$ javac PrivateConstructor.java
$ jar cvf patch.jar PrivateConstructor.class
Run Code Online (Sandbox Code Playgroud)

现在是有趣的部分.

创建一个扩展客户端文件夹中的PrivateConstructor的类.

public class ExtendedPrivateConstructor extends PrivateConstructor {


    public ExtendedPrivateConstructor(String test){
        super(test);
    }
}
Run Code Online (Sandbox Code Playgroud)

和一个主要的类来测试它

public class Main {

    public static void main(String str[])  {
       PrivateConstructor privateConstructor = new ExtendedPrivateConstructor("Gotcha");
       System.out.println(privateConstructor);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在编译client文件夹的源文件patch.jar

 $ javac -cp ..\patch\patch.jar ExtendedPrivateConstructor.java Main.java
Run Code Online (Sandbox Code Playgroud)

现在用类路径上的两个jar运行它,看看会发生什么.

如果patch.jar到来之前的private.jarPrivateConstructor类是从加载patch.jar,,因为应用程序类加载器是一个URLClassLoader.

 $ java -cp .;..\patch\patch.jar;..\private\private.jar  Main // This works
 $ java -cp .;..\private\private.jar;..\patch\patch.jar  Main // This will fail
Run Code Online (Sandbox Code Playgroud)


inf*_*thi 5

@René Link 的解决方案已经足够好了,但在我的情况下不是:我写了我正在破解 Eclipse IDE 插件,这意味着我们在 OSGi 下工作,这意味着我们无法控制类路径解析顺序(它将在我们的包中加载我们的“被黑”类,在另一个包中加载普通的受害者类,它将使用不同的类加载器来执行此操作,然后我们将在将这些对象相互转换时遇到问题)。可能 OSGi 有一些工具可以解决这个问题,但我不太了解,而且我也没有找到这方面的信息。

所以我们发明了另一种解决方案。它比前一个更糟糕,但至少它适用于我们的情况(因此它更灵活)。

解决方案很简单:javaagent。它是一个标准工具,允许在加载字节码时对其进行操作。因此通过使用它和java ASM库解决了任务:修改受害者的字节码以使其构造函数公开,其余的很容易。

    public class MyAgent {
        public static void premain(String agentArguments, Instrumentation instrumentation) {
            instrumentation.addTransformer(new ClassFileTransformer() {

                @Override
                public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
                    throws IllegalClassFormatException {
                    if (className.equals("org/victim/PrivateClass")) { //name of class you want to modify
                        try {
                            ClassReader cr = new ClassReader(classfileBuffer);
                            ClassNode cn = new ClassNode();
                            cr.accept(cn, 0);

                            for (Object methodInst : cn.methods) {
                                MethodNode method = (MethodNode) methodInst;
                                if (method.name.equals("<init>") && method.desc.equals("()V")) { //we get constructor with no arguments, you can filter whatever you want
                                    method.access &= ~Opcodes.ACC_PRIVATE;
                                    method.access |= Opcodes.ACC_PUBLIC; //removed "private" flag, set "public" flag
                                }
                            }
                            ClassWriter result = new ClassWriter(0);
                            cn.accept(result);
                            return result.toByteArray();
                        } catch (Throwable e) {
                            return null; //or you can somehow log failure here
                        }
                    }
                    return null;
                }
            });
        }
    }
Run Code Online (Sandbox Code Playgroud)

接下来必须使用 JVM 标志激活这个 javaagent,然后一切正常:现在您可以拥有可以毫无问题地调用 super() 构造函数的子类。或者这会炸掉你的整条腿。