Создание динамического подкласса 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);
}
}
}