32

How can I do this:

class Foo {
  public static Foo get() throws Exception {
    ClassLoader cl = new URLClassLoader(new URL[]{"foo.jar"}, null); // Foo.class is in foo.jar
    return (Foo)cl.loadClass("Foo").newInstance(); // fails on class cast
  }
}

What I need is for the JVM to consider the Foo instance from cl as if it is an instance of Foo from the classloader of the executing code.

I have seen these approaches, none of them good for me (the above example is a toy example):

  1. Load the class (or a separate interface) by a class loader that is a parent of both the calling code and created classloader
  2. Serialize and deserialize the object.
IttayD
  • 28,271
  • 28
  • 124
  • 178

6 Answers6

32

Not possible. Class identity consists of the fully qualified name and the class loader.

Casting an object to a class with the same name loaded by different classloaders is no different than trying to cast a String to Integer, because those classes really could be completely different despite having the same name.

Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
  • 2
    So if we can't even cast these new instances, how can we utilize them? If all we can do is `Object obj = cl.loadClass("Foo").newInstance();`, then how do we call the methods of the new instance of Foo? – Pacerier Aug 22 '14 at 15:39
  • 2
    @Pacerier: well, you could use reflection. But a more practical case is to have the classes extend classes or implement interfaces from a parent classloader in the delegation hierarchy that are also available to the rest of the code. But in the example code, there is no parent classloader (second parameter to the constructor is null)... – Michael Borgwardt Aug 22 '14 at 15:50
  • Will the performance of the second option be faster than using reflection? Or is it true that it internally uses reflection anyway? – Pacerier Aug 23 '14 at 06:14
  • @Pacerier: the second option works exactly like classes from the same classloader would, so it's faster than using reflection; but that gap has been narrowing with every new version of the JVM anyway. – Michael Borgwardt Aug 23 '14 at 06:49
12

I just spent the last two days struggling with this exact issue and I finally got around the problem by using java reflection:

// 'source' is from another classloader
final Object source = events[0].getSource();

if (source.getClass().getName().equals("org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread")) {

    // I cannot cast to 'org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread'
    // so I invoke the method 'terminate()' manually
    Method method = source.getClass().getMethod("terminate", new Class[] {});
    method.invoke(source, new Object[] {});
}

Hope this helps someone.

viragoboy
  • 131
  • 1
  • 4
  • 3
    How can we do this without reflection? If every single method of every single object loaded by our classloader needs to be called via reflection, wouldn't that severely bog down the entire program? – Pacerier Aug 22 '14 at 15:41
10

If class which need be cast implements Serializable then:

private <T> T castObj(Object o) throws IOException, ClassNotFoundException {
    if (o != null) {
        ByteArrayOutputStream baous = new ByteArrayOutputStream();
        {
            ObjectOutputStream oos = new ObjectOutputStream(baous);
            try {
                oos.writeObject(o);
            } finally {
                try {
                    oos.close();
                } catch (Exception e) {
                }
            }
        }

        byte[] bb = baous.toByteArray();
        if (bb != null && bb.length > 0) {
            ByteArrayInputStream bais = new ByteArrayInputStream(bb);
            ObjectInputStream ois = new ObjectInputStream(bais);
            T res = (T) ois.readObject();
            return res;
        }
    }
    return null;
}

usage:

Object o1; // MyObj from different class loader
MyObj o2 = castObj(o1);
wit
  • 101
  • 1
  • 2
  • 1
    I just want to point out that using serialization is equivalent to deep copying fields between objects. However, it does not "copy" method bodies. So MyObj (A) loaded by the main classloader and MyObj (B) retrieved from the custom loader are different and just most fields content match. – Vortex Nov 15 '16 at 21:25
  • It has helped me a lot to resolve my problem while I was deploying dynamic web service...thanks @wit – Surajit Biswas Jul 30 '19 at 10:16
6

No possible to cast in different classLoader.

You have this workaround with Gson, example cast Object to YourObject (Object is a YourObject class but in other classLoader):

Object o = ... 
Gson gson = new Gson();
YourObject yo = gson.fromJson(gson.toJson(o), YourObject.class);

I use this workaround because I compile any java code in a WebApp (on Tomcat). This workaround run in production.

Stéphane GRILLON
  • 11,140
  • 10
  • 85
  • 154
  • 1
    That's brilliant! This may be a hack, but it solved a really annoying deployment problem for me. – wirefox Aug 23 '18 at 18:34
4

Perhaps something using interfaces and java.lang.reflect.Proxy would suit your preferences. Using an InvocationHandler that finds and invokes the relevant method on the target class. (Note, any mobile-code security you have will be shot through if you do this.)

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
0

This is an old post where I arrived because I wanted to do nearly the same thing, but a simpler version of it...

It actually works if both Foo and the loaded class (in my case from a .java classfile in another package) extend the same class, say, AbstractTestClass.

Pieces of code:

public AbstractTestClass load(String toLoad) {
    try{
        Class test = Class.forName("testsFolder.testLoadable");
        Constructor ctorlist[] = test.getDeclaredConstructors();
        for(Constructor aConstructor : ctorlist){
           if(...){// find the good constructor
              Object loadedTest = aConstructor.newInstance(new Object[]{/*params*/});
              return (AbstractTestClass) test;
           }
        }
    }catch(...){}
    return new defaultTestClass();
}

This way I can insert the loaded class in an ArrayList<AbstractTestClass>.

Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
Aname
  • 515
  • 4
  • 16
  • 1
    you load 'test' from the same class loader. Furthermore, even if you specify another one, it will need to be a child of the current class loader or you wouldn't be able to cast to AbstractTestClass – IttayD Apr 26 '15 at 15:47
  • @IttayD you're right, it needs to extend the parent class (Foo in the question). Was it not the point of the question: "to consider the Foo instance from cl as if it is an instance of Foo"? – Aname May 18 '15 at 10:10