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 实现,因此如果被要求提供 BeanInfo
的 Foo
,它将尝试首先加载 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 的名称传递给您的自定义加载程序并验证它,一开始代码有点多,但更清晰。
我正在尝试使用 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 实现,因此如果被要求提供 BeanInfo
的 Foo
,它将尝试首先加载 class FooBeanInfo
,如果失败,它将为 Foo
.
但是由于您的 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 的名称传递给您的自定义加载程序并验证它,一开始代码有点多,但更清晰。