简体   繁体   English

ASM动态子类创建-NoClassDefFoundError BeanInfo

[英]ASM Dynamic Sub Class Creation - NoClassDefFoundError BeanInfo

I am trying to create a sub class dynamically using ASM Framework. 我正在尝试使用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; 我不明白BeanInfo的内容; what is it? 它是什么? and How can I solve my issue? 以及如何解决我的问题?

Thanks. 谢谢。

so the issue was in the ClassLoader 所以问题出在ClassLoader中

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 DynamicClassLoader(contextClassLoader, classBytes, className).loadClass(className);
    }

    private static class DynamicClassLoader extends ClassLoader {
        private byte[] classBytes;
        private final String className;

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

        @Override
        public Class findClass(String className) throws ClassNotFoundException {
            if (StringUtils.equals(this.className, className)) {
                return defineClass(className, this.classBytes, 0, this.classBytes.length);
            }

            throw new ClassNotFoundException(className);
        }
    }
}

The problem is that your findClass implementation tries to return the one generated class, regardless of which class the caller asks for. 问题是您的findClass实现尝试返回一个生成的类,而不管调用者要求哪个类。 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 . BeanUtils类依赖于Introspector ,该Introspector允许为受检查的类提供可选的显式beaninfo实现,因此,如果要求提供FooBeanInfo ,它将尝试首先加载类FooBeanInfo ,如果失败,则将构造通用的bean信息。为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. 但是,由于您的findClass实现试图以错误的名称XpOWErhNBiBeanInfo来(重新)构造XpOWErhNBi类,而不是报告缺少XpOWErhNBiBeanInfo ,因此出现了问题。

You have to change your SubClassLoader to receive the expected name of the generated class. 您必须更改SubClassLoader才能收到生成的类的预期名称。 Then, you can change the findClass implementation to 然后,您可以将findClass实现更改为

@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. 一个简单但rawClassBytes解决方案是在rawClassBytes第一个类之后使rawClassBytesnull ,并为每个后续的类加载请求抛出ClassNotFoundException ,因为继承的标准loadClass实现保证仅针对尚未加载的类调用findClass ,只要您立即加载生成的类的程序逻辑不会更改,所有后续请求都是关于不同的不受支持的类。

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. 首先将生成的类的名称传递给您的自定义加载器并进行验证,这会增加一些代码,但更简洁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM