Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending class with only private constructors

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".

like image 584
infthi Avatar asked Oct 21 '13 10:10

infthi


2 Answers

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.

like image 193
infthi Avatar answered Nov 07 '22 21:11

infthi


It is possible (but a bad hack) if

  • you have the source code of the class with the private constructors or you can reconstitute it from bytecode
  • the class is loaded by the application class loader
  • you can modify the jvm's classpath

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.

  1. Take the source code of PrivateConstructorClass and copy it to a source file. The package and class name must not be changed.
  2. Change the constructors of the PrivateConstructorClass from private to protected.
  3. Re-compile the modified source file of PrivateConstructorClass.
  4. Package the compiled class file into a jar archive. E.g. called "patch.jar"
  5. Create a class that extends the first one and compile it against the class in the patch.jar
  6. Change the jvm's classpath so that the patch.jar is the first entry in the classpath.

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
like image 29
René Link Avatar answered Nov 07 '22 20:11

René Link