cast across classloader?
How can I do this:
class Foo {
public static Foo get() throws Exception {
ClassLoader cl = new URLClassLoader(new URL[]{"foo.jar"}, nul开发者_如何学Pythonl); // 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):
- Load the class (or a separate interface) by a class loader that is a parent of both the calling code and created classloader
- Serialize and deserialize the object.
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.
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.
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);
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.
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.)
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>
.
精彩评论