创建Lamda函数实例

时间:2018-03-23 12:40:55

标签: java class lambda jvm java-bytecode-asm

我试图理解lambda表达式并得到以下问题。我理解lambda-expression被invokedynamic编译为javac指令和indy的基本机制。

我有类加载器:

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创建和调用实例失败并显示NoClassDefFoundError。我尝试使用静态方法在Java中创建一个类,它起作用了:

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

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

LineNumberTable:
    line 5: 0

我试图将自己添加为mv.visitLineNumber(5, new Label());但不幸的是,它没有用。

我动态生成的课程有什么问题?

1 个答案:

答案 0 :(得分:1)

关键部分是MethodHandles.Lookup实例,它定义了lambda将存在的上下文。由于你在main方法中通过MethodHandles.lookup()创建了它,它封装了一个由上下文定义的类的上下文您的新类加载器不可见。您可以通过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);

但是,众所周知,不建议使用带有访问覆盖的反射,它会在Java 9中生成警告,并且可能在将来的版本中破坏,以及其他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指令返回功能接口的实例,其生成方式与第一个静态方法的方法引用完全相同。当然,这仅仅是为了演示逻辑,因为生成一个实现直接在其函数方法中执行动作的接口的类很容易,而不是要求元工厂生成一个委托类。