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

3

我在使用Unsafe.defineAnonymousClass()加载生成的字节码类时遇到了困难。我想知道如何使用匿名类对象来初始化另一个类(或匿名类)。

例如,以下面的Callee类为例,它的构造函数接受Callee2作为参数。

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

在运行时,我为Callee2和Callee生成了新的类,并且这些新的类都被Unsafe.defineAnonymousClass()加载。对于新的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 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]没有意义。因此,我真的很困惑如何将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)

这个异常显然是针对于我自己的,因为匿名类对于任何类加载器都是瞬态的。但是在我的情况下,我需要通过新的Callee2实例来初始化新的Callee(Callee构造函数中的字节码将读取Callee2的字段成员),那么是否存在绕过此异常并将新的Callee2实例传递给新的Callee构造函数的方法?


我在这个问题上远非专家,但很明显Callee2类的字节码没有正确地注入到类加载器中。这就是为什么它无法加载Calle2类的原因。我曾经生成过动态代码(很久以前),使用编译器工具进行编译并注入,但采用了不同的方法。有关详细信息,请单击此处 - fps
1个回答

1
请查看签名和文档注释,它不在标准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);
在编写代码时,您可以提供一个与将要定义的类的常量池大小相同的数组。在不想修改的位置保留null。在常量池中有表示匿名类的CONSTANT_Class_info的地方,只需在数组中传递相关的Class对象即可。因此,就不需要查找类,甚至不必在类字节中提供正确的类名。
显然,这种方法存在一些限制:
  • 如果存在循环依赖,将会出现问题,因为您需要已经存在的Class对象来修补另一个类的池。对于已知会被懒加载解决的类使用,它可能有效。
  • 您只能将CONSTANT_Class_info修补到一个Class中,这足以访问该类的成员或创建其新实例。但是,当匿名类是签名的一部分时,即您想要声明该类型的字段或使用具有该类型作为参数或返回类型的方法时,它并没有帮助。但是,您可以使用通过MethodHandle修补CONSTANT_InterfaceMethodref_info条目的选项来访问这些方法(啊嗯,一旦实现,因为我猜“NYI”表示“尚未实现”)...

我忘了提到 CONSTANT_Class_info 可以用于类型转换。所以为解决签名问题,构造函数必须接受 Object(或任何其他非匿名超类)并将其转换为匿名类。 - Holger
感谢@Holger。我编写了一个新的基类,所有生成的类都是由它扩展而来。这个新的基类被用作所有构造函数的参数。这类似于ASM包中的编译示例。 - shijie xu
我回来看看你在这里构建cpPatch的建议,因为我的现有解决方案不能覆盖所有情况。假设由Unsafe加载的byte[] data表示一个类DAnouny,如何在此处为defineAnonymousClass(,,cpPatch)构建cpPatch(未声明CONSTANT_Class_info)?听起来cpPatch应该是从概念(https://gist.github.com/forax/0c25deac1867d1ef3247)中映射出来的。 - shijie xu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接