jvm 创建Lambda函数示例

zdwk9cvp  于 2022-12-18  发布在  其他
关注(0)|答案(1)|浏览(139)

我正在尝试理解lambda表达式,但遇到了以下问题。我知道lambda表达式是由javacindy的基本机制编译成invokedynamic指令的。
我有类加载器:

public class MyClassLoader extends ClassLoader{
    public Class<?> defineClass(byte[] classData){
        Class<?> cls = defineClass(null, classData, 0, classData.length);
        resolveClass(cls); 
        return cls; //should be ok, resolved before returning
    }
}

现在我想用ASM动态地创建一个手工制作的Class,并在LambdaMetafactory中使用它来创建我的功能接口的示例。

@FunctionalInterface
public interface Fnct {
    Object apply(String str);
}

以下是我的完整申请表:

public static void main(String[] args) throws Throwable {
    System.out.println(
          generateClassWithStaticMethod().getMethod("apply", String.class)
                 .invoke(null, "test")   
    ); //prints 3 as expected

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh = lookup.findStatic(generateClassWithStaticMethod(), "apply", MethodType.methodType(Object.class, String.class));
    Fnct f =  (Fnct) LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Fnct.class),
            mh.type(), mh, mh.type()).getTarget().invokeExact();

    f.apply("test"); //throws java.lang.NoClassDefFoundError: MyTestClass
}

public static Class<?> generateClassWithStaticMethod(){
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

    classWriter.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "MyTestClass", null, getInternalName(Object.class), null);

    MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC + ACC_STATIC, "apply", "(Ljava/lang/String;)Ljava/lang/Object;",null, null);
    mv.visitInsn(ICONST_3);
    mv.visitMethodInsn(INVOKESTATIC, getInternalName(Integer.class), "valueOf", "(I)Ljava/lang/Integer;", false);
    mv.visitInsn(ARETURN);
    mv.visitMaxs(0, 0);
    mv.visitEnd();

    return new MyClassLoader().defineClass(classWriter. toByteArray());
}


所以反射方法调用成功了,但是用LambdaMetafactory创建和调用示例失败了。我试着用一个静态方法在Java中创建一个类,它成功了:

public class Fffnct {
    public static Object apply(String str){
        return 3;
    }
}

我发现类文件的唯一区别是javac生成:

LineNumberTable:
    line 5: 0

我试着自己添加它为mv.visitLineNumber(5, new Label());,但不幸的是,它没有工作。
动态生成的类有什么问题?

knsnq2tg

knsnq2tg1#

最关键的部分是MethodHandles.Lookup示例,它定义了lambda所在的上下文。因为你已经通过MethodHandles.lookup()在main方法中创建了它,所以它封装了一个上下文,其中你的新类加载器定义的类是不可见的。你可以通过in(Class)更改上下文,但是这将更改访问模式,并导致LambdaMetaFactory拒绝查找对象。在Java 8中,不存在创建具有对另一个类的私有访问的查找对象的标准方式。
仅出于演示目的,我们可以使用带有访问覆盖的Reflection来生成一个适当的查找对象,以显示它将在以下情况下工作:

Class<?> generated = generateClassWithStaticMethod();
MethodHandles.Lookup lookup = MethodHandles.lookup().in(generated);
Field modes = MethodHandles.Lookup.class.getDeclaredField("allowedModes");
modes.setAccessible(true);
modes.set(lookup, -1);
MethodHandle mh = lookup.findStatic(generated, "apply", MethodType.methodType(Object.class, String.class));
Fnct f =  (Fnct) LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Fnct.class),
        mh.type(), mh, mh.type()).getTarget().invokeExact();
Object result = f.apply("test");
System.out.println("result: "+result);

但是,正如我们所知,不鼓励使用带有访问覆盖的Reflection,它将在Java9中生成一个警告,并且可能在未来的版本中中断,其他JRE可能甚至没有这个字段。
另一方面,Java 9引入了一种新的方法来获取查找对象,如果当前的模块依赖关系不禁止的话:

Class<?> generated = generateClassWithStaticMethod();
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup = MethodHandles.privateLookupIn(generated, lookup);// Java 9
MethodHandle mh = lookup.findStatic(generated, "apply", MethodType.methodType(Object.class, String.class));
Fnct f =  (Fnct) LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Fnct.class),
        mh.type(), mh, mh.type()).getTarget().invokeExact();
Object result = f.apply("test");
System.out.println("result: "+result);

Java 9引入的另一个选项是将类生成到您自己的包中,而不是新的类加载器中,然后您可以访问您自己的类查找上下文:

public static void main(String[] args) throws Throwable {
    byte[] code = generateClassWithStaticMethod();
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    Class<?> generated = lookup.defineClass(code);// Java 9
    System.out.println("generated "+generated);
    MethodHandle mh = lookup.findStatic(generated, "apply", MethodType.methodType(Object.class, String.class));
    Fnct f =  (Fnct) LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Fnct.class),
            mh.type(), mh, mh.type()).getTarget().invokeExact();
    Object result = f.apply("test");
    System.out.println("result: "+result);
}

public static byte[] generateClassWithStaticMethod() {
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    classWriter.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "MyTestClass", null, "java/lang/Object", null);
    MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC + ACC_STATIC, "apply", "(Ljava/lang/String;)Ljava/lang/Object;",null, null);
    mv.visitInsn(ICONST_3);
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
    mv.visitInsn(ARETURN);
    mv.visitMaxs(0, 0);
    mv.visitEnd();
    byte[] byteArray = classWriter.toByteArray();
    return byteArray;
}


如果您继续使用定制类加载器,您可以利用您正在进行代码生成这一事实。因此,您可以在生成的类中生成一个调用MethodHandles.lookup()的方法并返回它。然后,通过Reflection调用它,您就可以处理一个表示生成的类的上下文的查找对象。另一方面,你也可以把生成lambda示例的指令直接插入到生成的类中:

public static void main(String[] args) throws Throwable {
    String staticMethodName = "apply";
    MethodType staticMethodType = MethodType.methodType(Object.class, String.class);
    Class<?> generated = generateClassWithStaticMethod("TestClass", Object.class,
        staticMethodName, staticMethodType, Fnct.class, "apply", staticMethodType);
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    System.out.println("generated "+generated);
    MethodHandle mh = lookup.findStatic(generated, "apply", MethodType.methodType(Fnct.class));
    Fnct f =  (Fnct)mh.invokeExact();
    Object result = f.apply("test");
    System.out.println("result: "+result);
}

public static Class<?> generateClassWithStaticMethod(String clName, Class<?> superClass,
    String methodName, MethodType methodType, Class<?> funcInterface, String funcName, MethodType funcType) {

    Class<?> boxedInt = Integer.class;
    ClassWriter classWriter = new ClassWriter(0);
    classWriter.visit(V1_8, ACC_PUBLIC|ACC_SUPER, clName, null, getInternalName(superClass), null);
    MethodVisitor mv = classWriter.visitMethod(
         ACC_PUBLIC|ACC_STATIC, methodName, methodType.toMethodDescriptorString(), null, null);
    mv.visitInsn(ICONST_3);
    mv.visitMethodInsn(INVOKESTATIC, getInternalName(boxedInt), "valueOf",
        MethodType.methodType(boxedInt, int.class).toMethodDescriptorString(), false);
    mv.visitInsn(ARETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();
    String noArgReturnsFunc = MethodType.methodType(funcInterface).toMethodDescriptorString();
    mv = classWriter.visitMethod(ACC_PUBLIC|ACC_STATIC, methodName, noArgReturnsFunc, null, null);
    Type funcTypeASM = Type.getMethodType(funcType.toMethodDescriptorString());
    mv.visitInvokeDynamicInsn(funcName, noArgReturnsFunc, new Handle(H_INVOKESTATIC,
        getInternalName(LambdaMetafactory.class), "metafactory", MethodType.methodType(CallSite.class,
            MethodHandles.Lookup.class, String.class, MethodType.class, MethodType.class,
            MethodHandle.class, MethodType.class).toMethodDescriptorString()), funcTypeASM,
            new Handle(H_INVOKESTATIC, clName, methodName, methodType.toMethodDescriptorString()),
            funcTypeASM
        );
    mv.visitInsn(ARETURN);
    mv.visitMaxs(1, 0);
    mv.visitEnd();
    return new MyClassLoader().defineClass(classWriter.toByteArray());
}

这将生成第二个名称相同但没有参数的静态方法,返回函数接口的示例,生成的示例与第一个静态方法的方法引用完全一样,使用一条invokedynamic指令。当然,这只是为了演示逻辑,因为生成一个实现接口的类直接在其函数方法中执行操作是很容易的。而不是要求 meta工厂生成委托类。

相关问题