infthi
infthi

Reputation: 557

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

Upvotes: 13

Views: 8423

Answers (3)

infthi
infthi

Reputation: 557

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.

Upvotes: 6

Chris Knight
Chris Knight

Reputation: 25074

EDIT: This clearly doesn't work with the newly posted code examples edited into the question above, but I will keep the answer here for future posterity should it help someone else.


One method available to you which may or may not work depending on your situation is to use the Delegation pattern. For example:

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();
    }
}

You now have a class, WrapperClass, which has the same API as PrivateClass but delegates all the functionality to PrivateClass (after doing some pre or post work itself). Obviously, WrapperClass is not associated with the type heirarchy of PrivateClass but can be setup to do everything PrivateClass can.

Upvotes: 0

Ren&#233; Link
Ren&#233; Link

Reputation: 51483

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

Upvotes: 8

Related Questions