0

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.

3 Answers 3

2

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).

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks very much for your suggestions, it would appear that the error was to do with some dynamically generated methods in the class. The methods in question erroneously made reference to the associated super class by its simple name rather then its internal fully qualified class name.
2

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.

3 Comments

Thanks for your suggestions,unbelievably the error wasn't to do with class loading it was due to my incorrect specification of the referenced subclass' name. (i.e I used the simple name rather then the fully qualified internal class name) - I should no better :)
I take your point about ASM, but I actually like that the fact that its so low-level as it gives you complete control over the generation/instrumentation of java classes. To date, I have used it on over half a dozen projects.
@GilesThompson And yet, you are still running into difficult scenarios. Its just a tip. ASM is great, but it makes it very easy to get things broken and there is always a corner case you haven't thought of.
0

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.

1 Comment

Just for further reference: With Java 8, the verifier error messages became much more readable.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.