Создание динамического подкласса ASM - NoClassDefFoundError BeanInfo

Я пытаюсь создать подкласс динамически с помощью ASM Framework. Я могу создать класс и создать его экземпляр. Но когда я пытаюсь сделать

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<? 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; что это? и как я могу решить мою проблему?

Благодарю.

2 ответа

Решение

Проблема в том, что ваш findClass Реализация пытается вернуть один сгенерированный класс, независимо от того, какой класс запрашивает вызывающая сторона. В некоторых ситуациях, если не удается загрузить класс, это правильная вещь, чтобы заставить работу работать.

BeanUtils класс опирается на Introspector которая позволяет опциональную явную реализацию beaninfo для проверяемого класса, поэтому, если запрашивается BeanInfo из Foo, он попробует загрузить класс FooBeanInfo во-первых, и если это не удастся, он создаст общую информацию о бине для Foo,

Но так как ваш findClass реализация пытается (пере) построить XpOWErhNBi класс под неправильным именем XpOWErhNBiBeanInfo вместо того, чтобы сообщать об отсутствии XpOWErhNBiBeanInfo, дела идут плохо.

Вы должны изменить свой SubClassLoader получить ожидаемое имя сгенерированного класса. Затем вы можете изменить 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);
}

Более простым, но хакерским решением было бы null снаружи rawClassBytes после первого класса строительства и бросить ClassNotFoundException для каждого последующего запроса загрузки класса в качестве унаследованного стандарта loadClass реализация гарантирует, чтобы вызвать findClass только для еще не загруженных классов, так что, пока логика вашей программы немедленной загрузки сгенерированного класса не изменится, все последующие запросы будут касаться разных неподдерживаемых классов.

Однако, поскольку критический момент заключается в том, что логика программы не должна меняться, я не рекомендую это хакерское, хрупкое решение. Передача имени сгенерированного класса вашему пользовательскому загрузчику и проверка его, сначала немного больше кода, но намного чище.

Поэтому проблема была в 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);
        }
    }
}
Другие вопросы по тегам