如何在另一个生成字节码 class 中使用匿名 class 实例

How to use an anonymous class instance in another generate bytecode class

我很难使用由 Unsafe.defineAnonymousClass() 加载的生成的字节码 class。我想知道如何使用匿名对象 class 来初始化另一个 class(或匿名 class)。

举个例子class下面的Callee,它的构造函数接受Callee2作为参数。

Class Callee{
    Callee2 _call2;
    public Callee(Callee2 callee2){
        ...
    }
}

在运行时,我为 Callee2 和 Callee 生成了新的 classes,并且两个新的 classes 都由 Unsafe.defineAnonymousClass() 加载。对于new Callee,构造函数也改为:

 public test.code.jit.asm.simple.Callee(test.code.jit.asm.simple.Callee2.1506553666);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: aload_0       
         1: invokespecial #65                 // Method java/lang/Object."<init>":()V
           ....
         8: return       

而 Callee2 生成的 class 名称是:

      class test.code.jit.asm.simple.Callee2/1506553666

我创建了一个“Callee2/1506553666”的实例,并想用它创建新的 Callee 实例,但是失败了:

        newCls.getConstructor(args).newInstance(objs); 

在哪里 args = [class test.code.jit.asm.simple.Callee2/1506553666]objs= [test.code.jit.asm.simple.Callee2/1506553666@39b0a038]

args[0] 没有意义,因为这个 class 是由匿名加载程序加载的(匿名 classes 没有被任何 class 加载程序引用)。所以我真的很困惑如何将objs数组中的对象传递给方法调用..

调用 getConstructor (args) 时发生异常,消息为:

java.lang.NoClassDefFoundError: test/code/jit/asm/simple/Callee2/1506553666
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2483)
    at java.lang.Class.getConstructor0(Class.java:2793)
    at java.lang.Class.getConstructor(Class.java:1708)
    at code.jit.asm.util.ReflectionUtil.adapt2GeneratedObject(ReflectionUtil.java:36)
    at code.jit.asm.services.BytecodeGenerator.generator(BytecodeGenerator.java:164)

这个例外对我来说很明显,因为匿名 class 对于任何 classloader 都是暂时的。但就我而言,我确实需要通过新的 Callee2 实例初始化新的 Callee(也是匿名 class)(Callee 的构造函数中的字节码将读取 Callee2 的字段成员),因此是否有任何解决方法来传递新的 callee2 实例新的被调用者的构造函数?

看看 the signature and documentation comment,它在标准 API 文档中不可用,因为它不是官方的一部分 API:

Define a class but do not make it known to the class loader or system dictionary. For each CP entry, the corresponding CP patch must either be null or have the a format that matches its tag:

  • Integer, Long, Float, Double: the corresponding wrapper object type from java.lang
  • Utf8: a string (must have suitable syntax if used as signature or name) Class: any java.lang.Class object
  • String: any object (not just a java.lang.String)
  • InterfaceMethodRef: (NYI) a method handle to invoke on that call site's arguments

… (params:)

cpPatches where non-null entries exist, they replace corresponding CP entries in data

public native Class<?> defineAnonymousClass(
                       Class<?> hostClass, byte[] data, Object[] cpPatches);

换句话说,你可以提供一个与你要定义的class的常量池大小相同的数组。将 nulls 保留在您不想修改的位置。就在你的常量池有一个代表匿名 class 的 CONSTANT_Class_info 的地方,你只需传递数组中关联的 Class 对象。因此,无需查找 class,您甚至不必在 class 字节中提供正确的 class 名称。

有一些明显的限制:

  • 如果你有循环依赖,就会出现问题,因为你需要一个已经存在的 Class 对象来修补另一个 class 的池。好吧,对于已知会延迟解析的 class 用途,它可能会起作用
  • 您只能将 CONSTANT_Class_info 修补为 Class 这足以满足,例如访问 class 的成员或创建它的新实例。但是,当匿名 class 是 签名 的一部分时,它没有帮助,即您想要声明该类型的字段或使用将其作为参数的方法或 return类型。但是您可以使用通过 MethodHandle 修补 CONSTANT_InterfaceMethodref_info 条目的选项来访问此类方法(咳咳,一旦实施,我猜“NYI”的意思是“尚未实施”)……