Search code examples
javaclassdynamicjava-bytecode-asmbeaninfo

ASM Dynamic Sub Class Creation - NoClassDefFoundError BeanInfo


I am trying to create a sub class dynamically using ASM Framework. I am able to create the class and instantiate it. But when I try to do

org.apache.commons.beanutils.BeanUtils.copyProperties(processedEntity, entity); 

It throws this exception:

java.lang.NoClassDefFoundError: com/wheelsup/app/benefits/service/XpOWErhNBiBeanInfo (wrong name: com/wheelsup/app/benefits/service/XpOWErhNBi)

Here's the code that I am using to create the subclass:

Class<? extends T> get() throws Exception {
    String superClassInternalName = getInternalName(superClass);

    String subClassSimpleName = RandomStringUtils.random(10, true, false);
    String subClassInternalName = getClass().getPackage().getName().replaceAll("\\.", "/").concat("/").concat(subClassSimpleName);
    String subClassName = getClass().getPackage().getName().concat(".").concat(subClassSimpleName);

    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    classWriter.visit(Opcodes.V1_6,
            ACC_PUBLIC,
            subClassInternalName,
            null,
            superClassInternalName,
            null);

    visitDefaultConstructor(classWriter, superClassInternalName);

    classWriter.visitEnd();

    return SubClassLoader.<T>init().load(classWriter.toByteArray(), subClassName);
}

private void visitDefaultConstructor(ClassWriter classWriter, String superClassInternalName) {
    MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
    methodVisitor.visitCode();
    methodVisitor.visitVarInsn(ALOAD, 0);
    methodVisitor.visitMethodInsn(INVOKESPECIAL, superClassInternalName, "<init>", "()V");
    methodVisitor.visitInsn(RETURN);
    methodVisitor.visitMaxs(0, 0);
    methodVisitor.visitEnd();
}

private static class SubClassLoader<T> {
    private final ClassLoader contextClassLoader;

    private SubClassLoader(ClassLoader contextClassLoader) {
        this.contextClassLoader = contextClassLoader;
    }

    static <U> SubClassLoader<U> init() {
        return new SubClassLoader<>(Thread.currentThread().getContextClassLoader());
    }

    @SuppressWarnings("unchecked")
    Class<? extends T> load(byte[] classBytes, String className) throws Exception {
        return (Class<? extends T>) new SubClassLoader.DynamicClassLoader(contextClassLoader, classBytes).loadClass(className);
    }

    private static class DynamicClassLoader extends ClassLoader {
        private byte[] rawClassBytes;

        private DynamicClassLoader(ClassLoader contextClassLoader, byte[] classBytes) {
            super(contextClassLoader);
            this.rawClassBytes = classBytes;
        }

        @Override
        public Class findClass(String name) {
            return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length);
        }
    }
}

I don't understand the BeanInfo thing; what is it? and How can I solve my issue?

Thanks.


Solution

  • The problem is that your findClass implementation tries to return the one generated class, regardless of which class the caller asks for. In some situations, failing to load a class, is the right thing to make an operation work.

    The BeanUtils class relies on the Introspector which allows an optional explicit beaninfo implementation for the inspected class, so if being asked for the BeanInfo of Foo, it will try to load a class FooBeanInfo first and if this fails, it will construct a generic bean info for Foo.

    But since your findClass implementation tries to (re)construct the XpOWErhNBi class under the wrong name XpOWErhNBiBeanInfo instead of reporting the absence of XpOWErhNBiBeanInfo, things go wrong.

    You have to change your SubClassLoader to receive the expected name of the generated class. Then, you can change the findClass implementation to

    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        if(!name.equals(expectedName))
            throw new ClassNotFoundException(name);
        return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length);
    }
    

    A simpler, but hacky, solution would be to null out the rawClassBytes after the first class construction and throw a ClassNotFoundException for each subsequent class loading request as the inherited standard loadClass implementation guarantees to invoke findClass for not already loaded classes only, so as long as your program logic of immediately loading the generated class does not change, all subsequent requests are about different, unsupported classes.

    However, since the critical point is that the program logic must not change, I don’t recommend that hacky, fragile solution. Passing the name of the generated class to your custom loader and verify it, is a bit more code at first, but much cleaner.