在运行时动态创建子类

时间:2014-11-26 00:55:24

标签: java instrumentation java-bytecode-asm

我目前正在开发一个自定义ORM框架并利用ASM在运行时动态生成子类。生成过程似乎完成正常,但是当我尝试实例化生成的类时,我得到一个“NoClassDefFoundError”。

错误似乎与Super类有关,而不是实际的子类。以下是子类生成方法的摘录:

private Class generateProxyClass(Class anEntitySuperClass,
                                 List<Field> fieldsToIntercept) throws ClassNotFoundException{
  String entitySuperClassName = this.convertToInternalName(anEntitySuperClass.getName());
  //String entityProxySubClassName = "com/flux/dynamic/".concat(anEntitySuperClass.getSimpleName()).concat("Proxy");
  String entityProxySubClassName = anEntitySuperClass.getSimpleName().concat("Proxy");

  ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
  cw.visit(V1_6,ACC_PUBLIC+ACC_SUPER,entityProxySubClassName,null,entitySuperClassName,null);
  cw.visitSource(entityProxySubClassName.concat(".java"),null);

  //create constructor
  MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,"<init>","()V",null,null);
  mv.visitCode();
  //have our consturctor initailise its super class.
  mv.visitVarInsn(ALOAD,0);
  mv.visitMethodInsn(INVOKESPECIAL,entitySuperClassName,"<init>","()V");
  mv.visitInsn(RETURN);
  mv.visitMaxs(0,0);
  mv.visitEnd();

  this.generateAndAppendProxyAccessorMethods(anEntitySuperClass,fieldsToIntercept, cw);
  cw.visitEnd();
  //at this point our class should be fully generated an include all required fields. next we
  //convert the class to a byte array and pass it in to our helper method to load an
  //actual class from the byte array.
  return this.loadProxyClass(cw.toByteArray(),entityProxySubClassName);
}

上面调用的“loadProxyClass”方法是一个基本实例化的辅助方法 并调用自定义ClassLoader以加载动态创建的类:

/**loads the generated proxy class from the provided bytes. */
private Class loadProxyClass(byte[] aGeneratedProxyClass,String proxyClassName) throws ClassNotFoundException{
  return new ProxyClassLoader(Thread.currentThread().getContextClassLoader(),aGeneratedProxyClass)
               .loadClass(this.convertToExternalName(proxyClassName));
}

ProxyClassLoader只是扩展ClassLoader并覆盖“findClass”方法,以便加载动态生成的类字节:

public class ProxyClassLoader extends ClassLoader {

  private byte[] rawClassBytes;

  public ProxyClassLoader(ClassLoader parentClassLoader,byte[] classBytes){
    super(parentClassLoader);
    this.rawClassBytes = classBytes;
  }

  @Override
  public Class findClass(String name) {
    return defineClass(name,this.rawClassBytes, 0,this.rawClassBytes.length);
  }
}

我得到的错误是:Exception in thread "main" java.lang.NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy)

DummyEntity是超类,我传入generateProxyClass方法而DummyEntityProxy是我试图生成的类。我很难过,任何帮助都会非常感激。

3 个答案:

答案 0 :(得分:2)

您的异常消息显示问题:

  

线程中的异常&#34; main&#34; java.lang.NoClassDefFoundError:DummyEntity(错误的名字:DummyEntityProxy)

您的类加载器需要加载类DummyEntity,但链接的资源包含一个名为DummyEntityProxy的类。怎么会发生这种情况?这是您的类加载器findClass方法的实现:

@Override
public Class findClass(String name) {
  return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length);
}

您无法区分尝试加载的类,但您使用它知道的唯一类name的字节表示来定义DummyEntityProxy的任何类。而是实施:

@Override
public Class findClass(String name) {
  if (!name.equals(entityProxySubClassName)) {
    throw new ClassNotFoundException(name);
  }
  return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length);
}

这样,您确保没有定义另一个名称的类。但是,似乎首先不应该为该类查询ProxyClassLoader,而是其中一个父母应该已成功解决它。

似乎ASM是一个非常低级的API,可满足您的需求。您是否考虑过更高级的API,例如我的库Byte Buddy?其他ORM如Hibernate或Eclipse链接也在该级别上使用API​​,因为您正在努力解决的问题很难做到。

答案 1 :(得分:2)

通常,实现一个试图返回同一个类的ClassLoader并不是一个好主意,无论它被要求什么。您可以通过以下错误完美地说明这一点:NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy)。系统向您的ClassLoader询问了名为DummyEntity的类,并返回了一个名为DummyEntityProxy的类。

剩下的问题是为什么你的装载机被要求提供该类,因为通常首先询问父装载机。似乎父加载器没有找到超类,它指示您使用的父类加载器(Thread.currentThread().getContextClassLoader())无法访问您的超类。如果您使用anEntitySuperClass.getClassLoader()作为父加载器,那会更容易。

当然,您必须确保anEntitySuperClass的类加载器可以访问生成的代理使用的所有其他类。如果没有,您可能需要一个非常复杂的加载器委托结构来使两组类都可用。它甚至可能是不可能的(这取决于你的代理实际上应该做什么)。

答案 2 :(得分:0)

非常感谢你的建议。经过几个小时的修补后,我设法解决了这个错误。似乎该错误归因于该方法:

this.generateAndAppendProxyAccessorMethods(anEntitySuperClass,fieldsToIntercept, cw);

更具体地说,此方法生成的某些代码通过其简单名称而不是其内部完全限定类名错误地引用了超类。为了简洁起见,我从我的问题中省略了这种方法的实现,也因为我真的没想到问题与这种方法有关。通常,当动态生成的字节码逻辑中出现错误时,很难确定原因,因为JVM错误消息非常模糊。