Java cast from subclass to superclass under different classloader - java

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.

Related

The ClassLoader can replace the array by anything

When I run the following Java code:
ClassLoader c = new ClassLoader() {
#Override
public Class<?> findClass(String name) {
return Object.class;
}
};
Class<?> cc = c.loadClass(Object[][].class.getName());
System.out.println(cc.getName());
I get java.lang.Object in the display terminal, even if I replace Object[][].class.getName() by [[Ljava.lang.Object in the code. The problem is that I was expecting the console to show [[Ljava.lang.Object.
In effect, in the JVM specification, I can read the following:
An array class is created directly by the Java Virtual Machine (§5.3.3), not by a class loader. However, the defining class loader of D is used in the process of creating array class C.
Since Object[][] is an array class, I assumed that my findClass wouldn't be called with the argument [[Ljava.lang.Object but with its element type java.lang.Object.
Further, in the section "Creating Array Classes", the recursive algorithm is actually described:
If the component type is a reference type, the algorithm of this section (§5.3) is applied recursively using class loader L in order to load and thereby create the component type of C.
So my questions are:
Why am I getting this output? Does it mean that I have to manually include this recursive algorithm inside my ClassLoader, rather than letting the JVM doing it for me? If this is what it means, what is the best way to do it?
Am I misinterpreting the "created" in the first quotation? Does it only mean that I can't create the runtime array class, but that I still can patch its loading?
You're asking about the JVM specification, but your test demonstrates the behavior of java.lang.ClassLoader, an independent class which is "invoked by the Java virtual machine to resolve class references". If the JVM is loading an array class, it will bypass the class loader entirely. This can be demonstrated by letting the JVM try to load the class with a custom class loader:
Class<?> clazz = Class.forName("[[Lcom.foo.Test;", true, new ClassLoader() {
#Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
System.out.println("Loading " + name);
return super.loadClass(name, resolve);
}
});
System.out.println(clazz);
Output:
Loading com.foo.Test
class [[Lcom.foo.Test;
As you can see, the component type is initially loaded via the class loader, but the array types are loaded implicitly.

How to reload a class so that a annotation becomes visible?

In a question I asked earlier I got to know that in order to really be sure that some annotation is present or not somewhere in a class I need to reload it with a classloader that has access to both - the annotation and the class.
Now I'm struggling with how such a classloader would work. In my setup I just have the annotation as a java.lang.Class instance and the class that might be annotated with that annotation also as a java.lang.Class instance. Both might be loaded by some different classloaders I don't know anything about (classes might be loaded remotely, so they are not on the local file system).
While searching I found this JoinClassLoader
/**
* A class loader that combines multiple class loaders into one.<br>
* The classes loaded by this class loader are associated with this class loader,
* i.e. Class.getClassLoader() points to this class loader.
* <p>
* Author Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland, www.source-code.biz<br>
* License: LGPL, http://www.gnu.org/licenses/lgpl.html<br>
* Please contact the author if you need another license.
*/
public class JoinClassLoader extends ClassLoader {
private ClassLoader[] delegateClassLoaders;
public JoinClassLoader (ClassLoader parent, ClassLoader... delegateClassLoaders) {
super (parent);
this.delegateClassLoaders = delegateClassLoaders; }
protected Class<?> findClass (String name) throws ClassNotFoundException {
// It would be easier to call the loadClass() methods of the delegateClassLoaders
// here, but we have to load the class from the byte code ourselves, because we
// need it to be associated with our class loader.
String path = name.replace('.', '/') + ".class";
URL url = findResource(path);
if (url == null) {
throw new ClassNotFoundException (name); }
ByteBuffer byteCode;
try {
byteCode = loadResource(url); }
catch (IOException e) {
throw new ClassNotFoundException (name, e); }
return defineClass(name, byteCode, null); }
// some code omitted
} // end class JoinClassLoader
So my question is this:
Given a class instance of an arbitrary class C and a class instance of an annotation class A that may be loaded by arbitrary classloaders. A JoinClassLoader is instantiated with the classloaders of C and A in this order as delegating classloaders. Will that JoinClassLoader reload class C upon invoking findClass so that annotation A is always visible when C was actually annotated with it? If not how would such a classloader actually look like?
In a question I asked earlier I got to know that in order to really be
sure that some annotation is present or not somewhere in a class I
need to reload it with a classloader that has access to both - the
annotation and the class.
Given a class that has already been loaded by a classloader which might not have had access to all the annotations, I can believe that to be true. I think you've drawn the wrong conclusion, however.
If you want to be able to reflectively analyze a class's annotations at runtime, then the best solution is not to reload it. Instead, you should ensure that it is loaded in the first place by a classloader that can also see the annotations of interest. (And if that turns out not to be sufficient, then I don't see how you can expect reloading to help.)
In any case, reloading the class gives you a different class (of the same name), even if its bytecode is identical to that of the previously-loaded version. It is tricky to use this for anything but reflective analysis, and it is very difficult to be certain that the two classes actually do have identical bytecode. Reloading certainly does not replace the existing class with the newly-loaded one. All manner of fun can ensue.

Java ClassLoader not caching the class

I am trying to force some of the classes to be loaded using my custom class loader, the problem is after the loading the calling class still doesn't know about the class definition and tries to load it again, of course after that we have two different definitions of the class and assigning one to the other results in class cast exception.
Any pointers or ideas how this can be fixed?
This is the calling class:
CustomClassLoader loader = new CustomClassLoader(this.getPackageCodePath());
Class<?> midletClass = loader.loadClass(className);
midletClass.getMethod("InitEngine", Class.forName("android.app.Activity")).invoke(null, this);
ms_MIDlet = (MIDlet)midletClass.newInstance();//ClassCastException
And this is the class loader itself
public class CustomClassLoader extends PathClassLoader
{
public CustomClassLoader(String path)
{
super(path, getSystemClassLoader());//we set the parent to be the system class loader so the loading gets done in this class
}
#Override
public InputStream getResourceAsStream(String resName)
{
//...do some resource loading here
}
}
The calling class is running in some ClassLoader A. This classloader knows where to find and load MIDlet.class. Otherwise line 4 would produce a ClassNotFoundException for the cast.
You also use an instance of CustomClassLoader to load the Midlet.class.
On line 4 this blows up because you are casting the Midlet instance loaded by CustomClassLoader to a Midlet instances loaded by the ClassLoader A.
One solution is to make the CustomClassLoader logic delegate to ClassLoader A before loading a class itself. Something along the lines of first delegating a loadClass call to parentLoader.loadClass or getResourceAsStream to parentLoader.getResourceAsStream. If those calls fail then you can do your custom resource lookup.
This approach will make sure that all Midlet.class'es are actually loaded by the same classloader. You can check the delegation example at Java ClassLoader delegation model?

Problem with Java Class.forName

I'm trying to use Class.forName to dynamically load a class from a .jar file on the filesystem at runtime. The class I am trying to load implements an interface in another .jar file, so I am using my own URLClassLoader to reference the two .jars.
The code works when it is called not in the context of the web app (I have tested this by copying and pasting the method into a separate program and calling it from main). However, when I run/debug the web app (I'm using NetBeans) the code fails. The newInstance method throws a ClassCastException when I try to cast the instance to the interface specified in my jar_file_dependencies.jar.
Here is the relevant code if this helps:
File gameJar = new File("C:\\file_path\\jar_file.jar");
File gameDependenciesJar = new File("C:\\file_path\\jar_file_dependencies.jar");
URLClassLoader cl = new URLClassLoader(new URL[]
{
gameJar.toURI().toURL(),
gameDependenciesJar.toURI().toURL()
});
Class clazz = Class.forName("MyClass", true, cl);
IMyClass myClass = (IMyClass)clazz.newInstance();
System.out.println(game);
} catch (Exception e)
{
System.out.println(e.getMessage());
}
Any suggestions as to why this code is working in one program and not another would be greatly appreciated.
Many thanks,
Dan
short answer without going into too many of the hairy details: one or both of the gameJar and gameDependenciesJar probably contain a definition of the IMyClass class/interface. the rule of thumb when using child classloaders is that the child classloader should not contain any of the "shared" classes--these should exist only in the parent classloader.
partial explanation: Web app classloaders usually have different delegation policies from normal classloaders. often they prefer the child's class to the parent's. normal classloaders generally prefer the parent's class to the child's. in your web app, you are ending up with 2 separate definitions of the IMyClass class (one def in the parent classloader, one in the child). in your normal app, the IMyClass definition in the child classloader is being ignored, so only one definition gets loaded (in the parent classloader), and everything is happy.
Maybe this will help, (untested):
ClassLoader clsLoader = Thread.currentThread().getContextClassLoader();
if (clsLoader == null) {
clsLoader = this.getClass().getClassLoader();
}
URLClassLoader cl = new URLClassLoader(new URL[]
{
gameJar.toURI().toURL(),
gameDependenciesJar.toURI().toURL()
}, clsLoader);
Also, you should pass a full declarative name of the class MyClass instead of just calling it MyClass in Class.forName().
E.g.
Class clazz = Class.forName("com.xxxx.yyy.MyClass", true, cl);

ContextClassLoader not hooking

I'm trying to define a custom ClassLoader.
public class ExampleLoader extends ClassLoader
{
public Class<?> findClass(String name) throws ClassNotFoundException
{
System.out.println("This never gets printed");
return super.findClass(name);
}
public Class<?> loadClass(String name, boolean b)
throws ClassNotFoundException
{
System.out.println("This never gets printed");
return super.loadClass(name, b);
}
}
And of course my code to test it:
public class Tester
{
public static void main(String[] args)
{
Thread t = new FooThread();
t.setContextClassLoader(new ExampleLoader());
t.start();
}
}
class FooThread extends Thread
{
public void run()
{
new RandomClass();
}
}
The problem is that my lines never get printed. Clearly I'm missing something.
This is related to bug 4868493. Here's a cite of relevance:
Unfortunately the documentation for getContextClassLoader and
setContextClassLoader might lead one to the conclusion that the
submitter's code should work as expected.
However, there is a basic rule in class loading - no class can ever
automatically load a class which is "downstream", i.e. which cannot
be directly loaded by that class' ClassLoader or one of its ancestor
ClassLoaders.
This is described in a number of places. For example, meditate on
the white paper available here:
http://www.javageeks.com/Papers/ClassForName/index.html
to gain enlightenment.
The key point seems to be that the context class loader is not used
automatically by the Java language. It's only a conventional place to
store the context class loader so that other classes can use it with the
3-argument form of Class.forName.
The spec for Thread.getContextClassLoader and Thread.setContextClassLoader
should be clarified, and the meaning of "context class loader" should
be clarified. Re-classifying as a doc bug.
The spec has not been clarified yet.
To get it to work what you initially want, replace new RandomClass() by
Class.forName(RandomClass.class.getName(),
true,
getContextClassLoader()).newInstance();
This prints, contradictorily, the following:
This never gets printed
Normally, all classloaders in a JVM are organized in a hierarchy such that every classloader (except for the primordial classloader that bootstraps the entire JVM) has a single parent. When asked to load a class, every compliant classloader is expected to delegate loading to its parent first and attempt to define the class only if the parent fails.
Same thing is happening in your case. "RandomClass" is to be loaded, ContextClassLoader delegates to its parent an so on. And one of parent class loader was able to load "RandomClass" (RandomClass was in classpath of parent). Because of this reason your SOP doesn't show up.
Reference following article little old but good:
http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html?page=1

Categories

Resources