I have the following scenario. An application A is loaded by Classloader A. Application B is loaded by classloader B which is under Classloader A, meaning, Classloader B's parent is A.
Given this scenario, Application B can see the classes from application A because B's classloader delegates to A. What happens in the following scenario:
B calls A (B can see A because B's classloader delegates to A). A then tries to call B. To do this A needs to load classes from B. The class load fails with a class not found exception, and I understand why. A uses it's own classloader and therefore loads classes from either a parent classloader or it's own. B's classloader is further down the hierarchy and is therefore not visible to A. Is there any elegant solution to this kind of issue? It seems very strange that B can call A but A cannot call back to B.
I've managed to sort of work around this. If I pass B's classloader to A, then A can use that classloader to creat an instance of B. However, everything needs to be done using reflection. A cannot directly reference B, because A doesn't know about B. This is really frustrating, I have an instance of B but can't cast it to B because I get a ClassNotFound Exception. Any ideas? Below is code that
illustrates the problem:
URL[] urlsToLoadFrom = new URL[]{new URL("file:///C:/temp/subdir/")};
URLClassLoader loader1 = new URLClassLoader(urlsToLoadFrom);
URL[] urlsToLoadFrom2 = new URL[]{new URL("file:///C:/temp/subdir1/")};
URLClassLoader loader2 = new URLClassLoader(urlsToLoadFrom2,loader1);
URL[] urls = loader1.getURLs();
Class cls2 = Class.forName("ClassloaderServerTest",true,
loader1);
Class cls1 = Class.forName("ClassloaderClientTest",true,
loader2);
Object cst = cls2.newInstance();
Object cct = cls1.newInstance();
up to here I am fine, I have instances of my 2 classes. But if I try
the following:
ClassloaderClientTest client = (ClassloaderClientTest)cct;
I geta ClassNotFound Exception. The only way I can call any methods is
to use reflection. That really sucks.
The following works but is really ugly:
Class[] params = new Class[1];
params[0] = cls2;
Object[] methodParams = new Object[1];
methodParams[0] = cst;
Method testMethod = cls1.getMethod("test",params);
testMethod.invoke(cct, methodParams);
I found another workaround, namely use an interface that class A does know about. The code would be something like this:
ClassLoaderTestServerInterface cst = (ClassLoaderTestServerInterface)cls2.newInstance();
ClassLoaderTestClientInterface cct = (ClassLoaderTestClientInterface)cls1.newInstance();
What is also really strange is the following. Given the original scenario above, B calls A and passes A a parameter C. C's classloader (assuming C is some non-trivial object) will be B's classloader which is not visible to A, yet the parameter gets passed and A can see it and use it even though it is from a completely different and unaccessible classloader. How does that work?