Cast Across Classloader

Java cast from subclass to superclass under different classloader

 // true, why?
System.out.println(Object.class.isAssignableFrom(pClass));

this one should be entirely obvious. Object is java.lang.Object and you rather clumsily call super.loadClass if the fully qualified name starts with java. Which means the loader of Object.class is the system loader, and this is true for all load ops: Whether classLoader loads Parent, or the system loader does, they both work off of the notion that j.l.Object.class is loaded by the system loader: The same type, therefore, compatible.

// false, why? AppleScriptEngine is loaded by JAVA bootstrap-classloader
System.out.println(AppleScriptEngine.class.isAssignableFrom(myAppleScriptEngine));

same reason. In reverse: the fully qualified name of AppleScriptEngine is not starting with "java".

Class<?> myBufferedReader = classLoader.loadClass(MyBufferedReader.class.getName());
// true, why? BufferedReader is loaded by JAVA bootstrap-classlaoder
System.out.println(BufferedReader.class.isAssignableFrom(myBufferedReader));

you guessed it. Because the FQN of BufferedReader starts with "java".

Perhaps you've misunderstood the classloading model.

The model that classloaders employ is a parent/child relationship. A classloader has a parent.

Any class is loaded by some classloader; if it hits any other class in its source code it will ask its own classloader to load it. But that loader may defer the job to any other loader. That's important. Your code will defer for any class whose FQN starts with "java" (and not even "java.", which is a peculiar choice). Otherwise, it loads itself. The classloader that is on record as THE loader of a class is the one that invoked defineClass. In your code, if you go via the if block that checks for starting with "java", your loader does NOT invoke defineClass, and therefore isn't the loader. If that if is not taken, you always end up invoking defineClass, making you the loader.

The common model for classloaders is this:

  1. Ask your parent(s) to load the class, in order. If it can, great. We return that result, and that means the loader of said class is the parent and not you!

  2. If not, then this loader will load it. Conflicts are unlikely; after all, the system loader couldn't even find it. Now you are the loader.

ClassLoader itself supports this model, but you get it by overriding findClass and NOT loadClass. The default impl of loadClass will do precisely as above: First calls the parents' loadClass methods, and only if those can't find it, will it invoke findClass to finish the job.

I strongly recommend you follow this flow, and update your code to extend findClass, not loadClass.

If you really want to load it yourself and NOT delegate to your parent loaders, then, yeah, overriding loadClass is how you do it. But now you have to deal with the fact that if it is a class that your parent can also find, that you can run into the scenario where your loader loaded, say, com.foo.Example, and parent did too, and whilst those classes have exactly the same name, as far as the JVM is concerned, they are completely unrelated and entirely incompatible with each other. The JVM doesn't mind, but it leads to highly confusing scenarios, where an object of type com.foo.Example cannot be assigned to a variable of type... com.foo.Example.

If you must do this, note that checking if it starts with "java" is highly suboptimal. For starters, "java." is a better fit, and for seconds, not all system classes start with "java". Ask the system loader first, if it can load it, defer to that (just return what it found), at the very least.

What are you trying to accomplish by writing a loader? With that insight, I can give more advice on which method (loadClass or findClass) is appropriate to override.

Two ClassLoaders; One classloader needs to cast-down an Object but other classloader don't let it

The Mojo class loaded by your classloader and the Mojo class loaded by the other classloader are completely different classes as far as the JVM is concerned.

Some options:

  • Do whatever you need to in terms of invoking methods etc via reflection
  • Create a new Mojo based on the existing one via reflection, then using that (it depends what Mojo is really doing)
  • Change your classloader hierarchy so that one classloader delegates to another one, so that you can just cast.

You're in a fundamentally nasty position, and there are no easy workarounds that I'm aware of. If you can possibly fix the classloader hierarchy, it'll make your life a lot easier. Just having Mojo in a classloader which both of the other classloaders have as their parent would be enough.

ClassCastException because of classloaders?

You cannot cast between class loaders. Class identity is composed of fully qualified name and the class loader. Check class identity crysis here.

ClassCastException when casting to the same class

I am not quite following your description of the program flow, but usually when you get ClassCastExceptions you cannot explain you have loaded the class with one classloader then try to cast it to the same class loaded by another classloader. This will not work - they are represented by two different Class objects inside the JVM and the cast will fail.

There is an article about classloading in WebSphere. I cannot say how it applies to your application, but there are a number of possible solutions. I can think of at least:

  1. Change the context class loader manually. Requires that you can actually get a reference to an appropriate class loader, which may not be possible in your case.

    Thread.currentThread().setContextClassLoader(...);
  2. Make sure the class is loaded by a class loader higher in the hierarchy.

  3. Serialize and deserialize the object. (Yuck!)

There is probably a more appropriate way for your particular situation though.



Related Topics



Leave a reply



Submit