I'm trying to make a URLClassLoader which behaves as follows:
If asked for a class whose name is in a given set of inclusions, load it as normal
Otherwise, return a dummy class
I can't get this to work. In the attempt below, I expect the SpecialClassLoader to load Test$Thing succesfully. In doing so, I expect it to attempt to load Test$SuperThing, and I expect it to be okay about the fact that the dummy class Nothing is loaded instead.
However, something goes awry and a NoClassDefFoundError is thrown looking for Test$SuperThing.
Does anyone know how to fix this?
public class Test {
private static class SuperThing {}
private static class Thing extends SuperThing {}
public static void main(String[] args) {
Set<String> inclusions = new HashSet<String>();
inclusions.add("Test$Thing"); // note Test$SuperThing is excluded
URLClassLoader cl = (URLClassLoader)
Thread.currentThread().getContextClassLoader();
SpecialClassLoader cll =
new SpecialClassLoader(cl.getURLs(), inclusions);
try {
cll.loadClass("Test$Thing"); // line 22 (see stacktrace below)
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static class Nothing {}
private static final class SpecialClassLoader extends URLClassLoader {
private final Set<String> inclusions;
public SpecialClassLoader(URL[] urls, Set<String> inclusions) {
super(urls);
this.inclusions = inclusions;
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (inclusions.contains(name)) {
return findClass(name); // line 40 (see stacktrace below)
}
return Nothing.class;
}
}
}
EDIT: here is the stacktrace I get (line numbers 22 and 40 are indicated in listing above):
Exception in thread "main" java.lang.NoClassDefFoundError: Test$SuperThing
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
at java.net.URLClassLoader.access$000(URLClassLoader.java:56)
at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at Test$SpecialClassLoader.loadClass(Test.java:40)
at Test.main(Test.java:22)
Here is what is happening.
When you attempt to load Test$Thing, the defineClass method figures out that it needs to load the superclass of Test$Thing; i.e. Test$SuperThing.
The system classloader calls your classloader, which returns the Nothing class.
The system class loader says "Woah! that class doesn't have the right fully-qualified classname!!", and throws NoClassDefFoundError.
Basically, this system classloader is protecting against things that would destabilize the JVM. If it allowed a custom classloader to load the wrong class, nasty things could happen. For example, think about what might happen if some code invoked some method defined for the Test$SuperThing class after you had tricked classloader into loading the Nothing class.
If you want to do dirty tricks that substitute a dummy version of some class, you will need to generate bytecode on the fly, using the right fully qualified class name, the right superclass and interfaces, and methods with the right signatures. In short, you must satisfy the binary compatibility rules. If you don't, the system classloader will refuse to load your class ... no matter what you try to do in your custom classloader.
Frankly, you should be taking a completely different approach to whatever it is you are trying to do.
Can a class named Test$Thing.class found in the output of the compiler? As the code is given here it will be never directly referenced and maybe the compiler optimized the class away. In that case the reflection cannot find the class.
If that is the case you simply could add a (non-reflection) reference to the class, so that it will be compiled.
EDIT: After trying it out myself, I think I found the source of the problem. If I removed the 'extends SuperThing' from the Thing-declaration it complains about not finding java.lang.Object. Probably the classloader tries to load also all dependent classes (including the ones it extends) and cannot because they aren't on the whitelist. But that is a theory.
Related
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.
I'm currently developing a custom ORM framework and utilising ASM to dynamically generate sub classes at runtime. The generation process seems to complete OK, however when I try to instantiate the resulting class I'm getting a "NoClassDefFoundError".
The error seems to pertain to the Super class rather then the actual subclass. Here is an excerpt from the subclass generation method:
private Class generateProxyClass(Class anEntitySuperClass,
List<Field> fieldsToIntercept) throws ClassNotFoundException{
String entitySuperClassName = this.convertToInternalName(anEntitySuperClass.getName());
//String entityProxySubClassName = "com/flux/dynamic/".concat(anEntitySuperClass.getSimpleName()).concat("Proxy");
String entityProxySubClassName = anEntitySuperClass.getSimpleName().concat("Proxy");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_6,ACC_PUBLIC+ACC_SUPER,entityProxySubClassName,null,entitySuperClassName,null);
cw.visitSource(entityProxySubClassName.concat(".java"),null);
//create constructor
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,"<init>","()V",null,null);
mv.visitCode();
//have our consturctor initailise its super class.
mv.visitVarInsn(ALOAD,0);
mv.visitMethodInsn(INVOKESPECIAL,entitySuperClassName,"<init>","()V");
mv.visitInsn(RETURN);
mv.visitMaxs(0,0);
mv.visitEnd();
this.generateAndAppendProxyAccessorMethods(anEntitySuperClass,fieldsToIntercept, cw);
cw.visitEnd();
//at this point our class should be fully generated an include all required fields. next we
//convert the class to a byte array and pass it in to our helper method to load an
//actual class from the byte array.
return this.loadProxyClass(cw.toByteArray(),entityProxySubClassName);
}
The "loadProxyClass" method called above is a helper method that basically instantiates
and calls a custom ClassLoader in order to load the dynamically created class:
/**loads the generated proxy class from the provided bytes. */
private Class loadProxyClass(byte[] aGeneratedProxyClass,String proxyClassName) throws ClassNotFoundException{
return new ProxyClassLoader(Thread.currentThread().getContextClassLoader(),aGeneratedProxyClass)
.loadClass(this.convertToExternalName(proxyClassName));
}
The ProxyClassLoader simply extends ClassLoader and overrides the "findClass" method in order to load the Dynamically Generated class bytes:
public class ProxyClassLoader extends ClassLoader {
private byte[] rawClassBytes;
public ProxyClassLoader(ClassLoader parentClassLoader,byte[] classBytes){
super(parentClassLoader);
this.rawClassBytes = classBytes;
}
#Override
public Class findClass(String name) {
return defineClass(name,this.rawClassBytes, 0,this.rawClassBytes.length);
}
}
The error I get is: Exception in thread "main" java.lang.NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy)
Where the DummyEntity is the super class I pass into the generateProxyClass method and the DummyEntityProxy is the class I'm attempting to generate. I'm stumped, any help would be greatly appreciated.
Generally, it isn’t a good idea to implement a ClassLoader that tries to return the same class regardless of what it has been asked for. This is perfectly illustrated by the error you get: NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy). The system asked your ClassLoader for a class named DummyEntity and you returned a class named DummyEntityProxy.
The remaining question is why your loader has been asked for that class as usually the parent loader is asked first. It seems that the parent loader has not found the super class which indicates that the parent class loader you have used (Thread.currentThread().getContextClassLoader()) has no access to your super class. It would have been easier if you used anEntitySuperClass.getClassLoader() as parent loader.
Of course, you have to ensure that all other classes used by your generated proxy are accessible by anEntitySuperClass’s class loader. If not, you might need a very complex loader delegation structure to make both group of classes available. It might even be impossible (that depends on what your proxy actually ought to do).
The problem is revealed by your exception's message:
Exception in thread "main" java.lang.NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy)
Your class loader expected to load a class DummyEntity but the linked resource contained a class named DummyEntityProxy. How could that happen? It is your class loader's findClass method's implementation:
#Override
public Class findClass(String name) {
return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length);
}
You do not distinguish what class is attempted to be loaded but you define any class of name with the only class it knows, the DummyEntityProxy's byte representation. Rather implement:
#Override
public Class findClass(String name) {
if (!name.equals(entityProxySubClassName)) {
throw new ClassNotFoundException(name);
}
return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length);
}
This way, you are making sure that you are not defining a class of another name. It seems however as if the ProxyClassLoader should not be queried for the class in the first place but that one of its parents should have successfully resolved it.
It seems like ASM is quite a low-level API for your needs. Have you considered a more high-level API like for example my library Byte Buddy? Other ORMs like Hibernate or Eclipse link also use an API on that level, simply because the things you are struggling with are difficult to get right.
Thank you all very much for your suggestions. After many hours of tinkering I managed to resolve the error. It appears that the error was attributed to the method:
this.generateAndAppendProxyAccessorMethods(anEntitySuperClass,fieldsToIntercept, cw);
More specifically, some of the code generated by this method incorrectly referenced the super class by its simple name rather than its internal fully qualified class name. I omitted the implementation of this method from my question for brevity and also because I genuinely didn't expect that the problem was associated with this method. Generally, when errors occur in dynamically generated byte code logic it can be immensely difficult to pinpoint the cause, simply because JVM error messages are so ambiguous.
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.
I have a Custom Classloader : CustomClassLoader(extends ClassLoader)
I have a class : IntegerPrint
I load my class with my Custom ClassLoader. I was expecting SOPs in the below code to return the same value. But the first SOP prints "sun.misc.Launcher$AppClassLoader#.." & second SOP prints "CustomClassLoader#.."
Why it happens so? Please advise.
public class IntegerPrinterTest {
public static void main(String[] args) throws Exception {
CustomClassLoader loader = new CustomClassLoader(IntegerPrinterTest.class.getClassLoader());
Class<?> clazz = loader.loadClass("IntegerPrinter");
System.out.println(IntegerPrinter.class.getClassLoader());
System.out.println(clazz.getClassLoader());
}
}
What did you expect?
In
System.out.println(IntegerPrinter.class.getClassLoader());
you create a
Class<IntegerPrint>
object, and surely, its class (Class) must have been loaded by some class loader. It takes no genius to imagine that Class must have been loaded very early, even before your code even gains control.
Please run your example with
java -verbose:class ....
to see which classes are laoded in what order.
The first call:
IntegerPrinter.class.getClassLoader()
Will actually do:
IntegerPrinterTest.class.getClassLoader().loadClass("IntegerPrinter")
So it totally ignores your custom classloader.
In other words: your own classloader is not actually used for any objects you create using native calls like "new" etc. To do that it should be responsible for loading the IntegerPrinter class as well.
It is rather circumspect (and in general useless) to do it in the same class but you could do:
Class<?> clazz = loader.loadClass("IntegerPrinterTest");
clazz.getMethod("main").invoke(null);
(note this code is not tested but should approximate something that works)
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