Saturday, September 1, 2012

About Class.newInstance()

Today, a little story about a problem that I encountered recently, problem related to java reflection.

The problem is the following : I had a couple of Java classes, that happen to be Java beans and that I wanted to manipulate by reflection.
Actually, it's not me but a third part library, typically JPA related, that manipulates my objects and especially construct them by reflection using :

SomeObject obj = SomeObject.class.newInstance();

Fair enough, expect that to do this I must ensure that all my objects have a public constructor with no argument. And it's something that I don't have in my case, my objects implement the factory design pattern, and the constructors of my objects are private or protected, and thus failed the previous code.

So I thought, let's just use reflection to change the visibility of the constructor by a well known:

SomeObject.class.getConstructor().setAccessible(true);

The problem is that, this method works if I instantiate the object from the constructor that I just set accessible, but it will not make the constructor visible by my library when it will call newInstance() method on the class.
Why ? For a simple reason, the setAccessible method does not actually change the modifier of the constructor, but just set some override attribute in the Constructor object, and the newInstance()  check this boolean and does not throw an Exception if set.

But in the case of the of a newInstance() called on the Class object, the private method privateGetDeclaredConstructors(boolean publicOnly) is called, and this method does not rely on the Constructor object itself, but either ask the JVM by reading the byte code of the class, or uses the publicConstructors object that caches the public constructors of this class.
So if I add my modified constructor to the publicConstructors, let's say by reflection, it will do the trick. But the problem is that the publicConstructors is a SoftReference to an array of constructors. So if I do so, the change might disappear if my program run out memory and my modified constructor, softly referenced, is cleaned up.

So the only choice that remains is to change the byte code itself.
For that purpose, I will simply use the famous javassist library.

CtClass someClass= ClassPool.getDefault().get("sandbox.SomeObject");
someClass.getDeclaredConstructor(null).setModifiers(Modifier.PUBLIC);
someClass.writeFile();
someClass.toClass();

Just a couple of things I need to be careful of, I mustn't load the SomeObject class before changing the bytecode (at least on the ClassLoader that I will use to override this class, in my case the SystemClassLoader).
So no SomeObject.class.getCanonicalName() to get the name of the class, if I don't follow this rule, the someClass.toClass() method, that forces the (in this case System) class loader to load the class will throw a

java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "sandbox/SomeObject"

And we are done, the complete (and simple) code :

My dummy object :


package sandbox;

public class SomeObject {
    // private constructor
    private SomeObject() {    }
}

My main class :

package sandbox;

import java.lang.reflect.Modifier;
import javassist.ClassPool;
import javassist.CtClass;

public class Test {
 
    public static void main(String[] args) {
        try {
            // don't use SomeObject.class.getCanonicalName()
            CtClass classToEdit = ClassPool.getDefault().get("sandbox.SomeObject");
            classToEdit.getDeclaredConstructor(null).setModifiers(Modifier.PUBLIC);
            classToEdit.writeFile();
            classToEdit.toClass();

            // some stuffs
            ...
 
             // instantiate the object using the modified constructor
            SomeObject obj = SomeObject.class.newInstance();
            System.out.println(obj3);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2 comments:

  1. Thanks for the post, I am techno savvy. I believe you hit the nail right on the head. I am highly impressed with your blog. It is very nicely explained. Your article adds best knowledge to our Java Online Training from India. or learn thru Java EE Online Training Students.

    ReplyDelete