问题是:我有一个只有私有构造函数的类(我不能修改它的源代码),我需要扩展它。
由于反射允许我们随时创建这样的类的实例(通过获取构造函数并调用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);
}}
我想要做的就是构建我自己的包装器(就像这个一样)
class Extension extends Singleton {
@Override
public void doWork(String arg) {
super.doWork("Processed: " + arg);
}}
并使用反射将其注入Factory:
Singleton.class.getField("instance").set(null, new Extension());
但是我没有看到构造这样的对象的任何方法,因为它的超类的构造函数是私有的。问题是“这是否可能”。
答案 0 :(得分:7)
如果
,有可能(但是糟糕的黑客)您可以创建与原始类二进制兼容的补丁。
我将在下一节中调用您想要扩展PrivateConstructorClass的类。
PrivateConstructorClass
的源代码并将其复制到源文件中。不得更改包名和类名。PrivateConstructorClass
的构造函数从private更改为protected。PrivateConstructorClass
。现在有一些示例代码可以让您检查它是如何工作的:
期待以下文件夹结构
+-- workspace
+- private
+- patch
+- client
在PrivateConstructor
文件夹
private
课程
public class PrivateConstructor {
private String test;
private PrivateConstructor(String test){
this.test = test;
}
@Override
public String toString() {
return test;
}
}
在private
文件夹中打开命令提示符,编译并打包它。
$ javac PrivateConstructor.java
$ jar cvf private.jar PrivateConstructor.class
现在在patch
文件夹中创建补丁文件:
public class PrivateConstructor {
private String test;
protected PrivateConstructor(String test){
this.test = test;
}
@Override
public String toString() {
return test;
}
}
编译并打包
$ javac PrivateConstructor.java
$ jar cvf patch.jar PrivateConstructor.class
现在是有趣的部分。
创建一个扩展客户端文件夹中PrivateConstructor的类。
public class ExtendedPrivateConstructor extends PrivateConstructor {
public ExtendedPrivateConstructor(String test){
super(test);
}
}
和一个测试它的主类
public class Main {
public static void main(String str[]) {
PrivateConstructor privateConstructor = new ExtendedPrivateConstructor("Gotcha");
System.out.println(privateConstructor);
}
}
现在针对client
patch.jar
文件夹的源文件
$ javac -cp ..\patch\patch.jar ExtendedPrivateConstructor.java Main.java
现在用类路径上的两个jar运行它,看看会发生什么。
如果patch.jar
位于private.jar
之前,而PrivateConstructor
类是从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
答案 1 :(得分:3)
@RenéLink的解决方案已经足够了,但不是我的情况:我写的是我正在攻击Eclipse IDE插件,这意味着我们在OSGi下工作,这意味着我们无法控制类路径解析顺序(它将在我们的bundle中加载我们的“hacked”类,在另一个bundle中加载vanilla受害者类,并且它将使用不同的类加载器来执行此操作,然后我们会遇到将这些对象一个接一个地转换的问题)。可能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;
}
});
}
}
接下来必须使用JVM标志激活这个javaagent,然后一切正常:现在你可以拥有可以调用super()构造函数而没有任何问题的子类。或者这可能会让你的整条腿受伤。
答案 2 :(得分:0)
编辑:这显然不适用于上面问题中编辑的新发布的代码示例,但是我会在这里为未来的后代保留答案,如果它可以帮助其他人。
根据您的具体情况,您可以使用或不使用的一种方法是使用Delegation pattern。例如:
public class PrivateClass {
private PrivateClass instance = new PrivateClass();
private PrivateClass() {/*You can't subclass me!*/
public static PrivateClass getInstance() { return instance; }
public void doSomething() {}
}
public class WrapperClass {
private PrivateClass privateInstance = PrivateClass.getInstance();
public void doSomething() {
//your additional logic here
privateInstance.doSomething();
}
}
您现在有一个类WrapperClass
,它与PrivateClass具有相同的API,但是将所有功能委托给PrivateClass(在执行一些预处理或后期工作之后)。显然,WrapperClass
与PrivateClass
的类型层次关联无关,但可以设置为执行PrivateClass
所能做的所有事情。