I know that Class
instance loaded by different class loader can't be cast to each other.
But what if the one Class
extends the other? I did an experiment and the result is confusing. Here is the ClassLoader
I define:
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
if (name.startsWith("java")) {
return super.loadClass(name);
}
String filename = "/" + name.replaceAll("\\.", "/") + ".class";
InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (Throwable e) {
throw new ClassNotFoundException(name);
}
}
}
And the experiment code:
// These classes will be loaded by MyClassLoader
class Parent { }
class Child extends Parent { }
class MyCalendarData_aa_DJ extends CalendarData_aa_DJ { }
class MyAppleScriptEngine extends AppleScriptEngine { }
class MyBufferedReader extends BufferedReader {
public MyBufferedReader(Reader in) {
super(in);
}
}
public class DifferentClassLoaderCast {
public static void main(String[] args) throws Exception {
ClassLoader classLoader = new MyClassLoader();
Class<?> pClass = classLoader.loadClass(Parent.class.getName());
Class<?> cClass = classLoader.loadClass(Child.class.getName());
// true, as pClass and cClass are loaded by same classloader
System.out.println(pClass.isAssignableFrom(cClass));
// false, different classloader
System.out.println(Parent.class.isAssignableFrom(cClass));
// true, why?
System.out.println(Object.class.isAssignableFrom(pClass));
Class<?> myCalendarData_aa_DJClass = classLoader.loadClass(MyCalendarData_aa_DJ.class.getName());
// false, CalendarData_aa_DJ is loaded by JAVA ext-classloader
System.out.println(CalendarData_aa_DJ.class.isAssignableFrom(myCalendarData_aa_DJClass));
Class<?> myAppleScriptEngine = classLoader.loadClass(MyAppleScriptEngine.class.getName());
// false, why? AppleScriptEngine is loaded by JAVA bootstrap-classloader
System.out.println(AppleScriptEngine.class.isAssignableFrom(myAppleScriptEngine));
Class<?> myBufferedReader = classLoader.loadClass(MyBufferedReader.class.getName());
// true, why? BufferedReader is loaded by JAVA bootstrap-classlaoder
System.out.println(BufferedReader.class.isAssignableFrom(myBufferedReader));
}
}
It seems that subclass loaded by MyClassLoader
can be cast to superclass loaded by bootstrap class loader under package starts with java
or built-in class?
// 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:
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!
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.