The problem is: I have a class with only private constructor available (and I cannot modify it's source code), and I need to extend it.
Since reflections allow us to create instances of such classes whenever we want (with getting constructors and calling for newInstance()), is there any way to create an instance of an extended version of such class (I mean, really any way, even if it is against OOP)?
I know, it is a bad practice, but looks like I have no choice: I need to intercept some calls to one class (it is a singleton, and it's not an interface realization, so dynamic proxies do not work here).
Minimal example (as requested):
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);
}}
all I want to do is to construct my own wrapper (like this one)
class Extension extends Singleton {
@Override
public void doWork(String arg) {
super.doWork("Processed: " + arg);
}}
and the inject it into Factory using reflection:
Singleton.class.getField("instance").set(null, new Extension());
But I do not see any way to construct such object cause its superclass's constructor is private. The question is "is that possible at all".
The solution by @René Link was good enough, but not in my case: I wrote I'm hacking an Eclipse IDE plugin, and this means we're working under OSGi, and this means we cannot control the classpath resolving order (it will load our "hacked" class in our bundle, and vanilla victim class in another bundle, and it will do this with different classloaders, and then we would have problems with casting such objects one to another). Possibly OSGi has some tools to solve this problems, but I don't know it well enough, and also I found no info on this.
So we invented another solution. It is worse than previous one, but at least it works in our case (and so it's more flexible).
The solution is simple: javaagent. It's a standard tool, which allows to manipulate bytecode at the time it is loaded. So the task was solved by using it and java ASM library: the victim's bytecode was modified to make it's constructor public, the remaining was easy.
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;
}
});
}
}
Next this javaagent must be activated with JVM flag, and then everything just works: now you can have subclasses which can call super() constructor without any problem. Or this can blow your whole leg off.
It is possible (but a bad hack) if
You can than create a patch that is binary compatible with the original class.
I will call the class you want to extend PrivateConstructorClass in the following section.
PrivateConstructorClass
and copy it to a source file. The package and class name must not be changed.PrivateConstructorClass
from private to protected.PrivateConstructorClass
.Now some example code that let you examine how it works:
Expect the following folder structure
+-- workspace
+- private
+- patch
+- client
Create the PrivateConstructor
class in the private
folder
public class PrivateConstructor {
private String test;
private PrivateConstructor(String test){
this.test = test;
}
@Override
public String toString() {
return test;
}
}
Open a command prompt in the private
folder, compile and package it.
$ javac PrivateConstructor.java
$ jar cvf private.jar PrivateConstructor.class
Now create the patch file in the patch
folder:
public class PrivateConstructor {
private String test;
protected PrivateConstructor(String test){
this.test = test;
}
@Override
public String toString() {
return test;
}
}
Compile and package it
$ javac PrivateConstructor.java
$ jar cvf patch.jar PrivateConstructor.class
Now comes the interresting part.
Create a class that extends the PrivateConstructor in the client folder.
public class ExtendedPrivateConstructor extends PrivateConstructor {
public ExtendedPrivateConstructor(String test){
super(test);
}
}
and a main class to test it
public class Main {
public static void main(String str[]) {
PrivateConstructor privateConstructor = new ExtendedPrivateConstructor("Gotcha");
System.out.println(privateConstructor);
}
}
Now compile the client
folder's source files against the patch.jar
$ javac -cp ..\patch\patch.jar ExtendedPrivateConstructor.java Main.java
and now run it with both jars on the classpath and see what happens.
If the patch.jar
comes before the private.jar
than the PrivateConstructor
class is loaded from the patch.jar,
because the application class loader is a URLClassLoader
.
$ java -cp .;..\patch\patch.jar;..\private\private.jar Main // This works
$ java -cp .;..\private\private.jar;..\patch\patch.jar Main // This will fail
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With