ASM 动态子 Class 创建 - NoClassDefFoundError BeanInfo

ASM Dynamic Sub Class Creation - NoClassDefFoundError BeanInfo

我正在尝试使用 ASM 框架动态创建子 class。 我能够创建 class 并实例化它。但是当我尝试做

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

它抛出这个异常:

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

这是我用来创建子class的代码:

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);
        }
    }
}

我不明白 BeanInfo 的东西;它是什么?以及如何解决我的问题?

谢谢。

所以问题出在 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);
        }
    }
}

问题是您的 findClass 实现尝试 return 生成的 class,而不管调用者请求哪个 class。在某些情况下,未能加载 class 是使操作正常进行的正确做法。

BeanUtils class 依赖于 Introspector ,它允许为检查的 class 提供可选的显式 beaninfo 实现,因此如果被要求提供 BeanInfoFoo,它将尝试首先加载 class FooBeanInfo,如果失败,它将为 Foo.

构造一个通用 bean 信息

但是由于您的 findClass 实现试图(重新)构建 XpOWErhNBi class 错误的名称 XpOWErhNBiBeanInfo 而不是报告缺少 XpOWErhNBiBeanInfo, 出问题了。

您必须更改 SubClassLoader 才能接收生成的 class 的预期名称。然后,您可以将 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);
}

一个更简单但很老套的解决方案是在第一个 class 构造之后 null 退出 rawClassBytes 并为每个后续 [=42] 抛出一个 ClassNotFoundException =] 加载请求作为继承标准 loadClass 实现保证调用 findClass 仅用于尚未加载的 classes,因此只要您的程序逻辑立即加载生成的 class 不改,后续所有请求都差不多,不支持classes.

但是,由于关键点是程序逻辑不能改变,所以我不推荐那种 hacky、脆弱的解决方案。将生成的 class 的名称传递给您的自定义加载程序并验证它,一开始代码有点多,但更清晰。