创建 Lambda 函数实例
Creating Lamda function instance
我试图理解 lambda 表达式并遇到以下问题。我知道 lambda 表达式被 javac
和 indy
的基本机制编译为 invokedynamic
指令。
我有 class 加载程序:
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 中创建一个 class,它起作用了:
public class Fffnct {
public static Object apply(String str){
return 3;
}
}
我发现的 class 文件的唯一区别是 javac
生成:
LineNumberTable:
line 5: 0
我尝试自己添加为 mv.visitLineNumber(5, new Label());
但不幸的是,它没有用。
我动态生成的 class 有什么问题?
关键部分是 MethodHandles.Lookup
实例,它定义了 lambda 将存在的上下文。由于您已经在 main 方法中通过 MethodHandles.lookup()
创建了它,它封装了一个上下文,其中 class由您的新 class 加载程序定义的内容不可见。您可以通过 in(Class)
更改上下文,但这将更改访问模式并导致 LambdaMetaFactory
拒绝查找对象。在 Java 8 中,没有标准方法来创建对另一个 class.
具有私有访问权限的查找对象
仅出于演示目的,我们可以使用具有访问覆盖的反射来生成适当的查找对象,以表明它随后会起作用:
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);
Java9 引入的另一个选项是在您自己的包中生成 class 而不是新的 class 加载程序。然后,您自己的 class 查找上下文可以访问它:
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;
}
如果您继续使用自定义 class 加载器,您可以利用您正在生成代码这一事实。因此,您可以在生成的 class 中生成调用 MethodHandles.lookup()
的方法并返回它。然后,通过 Reflection 调用它,您将获得一个表示生成的 class 上下文的查找对象。另一方面,您也可以将生成 lambda 实例的指令直接插入生成的 class 本身:
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
指令。当然,这只是为了演示逻辑,因为很容易生成一个 class 实现直接在其函数方法中执行操作的接口,而不是要求元工厂生成一个委托 class .
我试图理解 lambda 表达式并遇到以下问题。我知道 lambda 表达式被 javac
和 indy
的基本机制编译为 invokedynamic
指令。
我有 class 加载程序:
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 中创建一个 class,它起作用了:
public class Fffnct {
public static Object apply(String str){
return 3;
}
}
我发现的 class 文件的唯一区别是 javac
生成:
LineNumberTable:
line 5: 0
我尝试自己添加为 mv.visitLineNumber(5, new Label());
但不幸的是,它没有用。
我动态生成的 class 有什么问题?
关键部分是 MethodHandles.Lookup
实例,它定义了 lambda 将存在的上下文。由于您已经在 main 方法中通过 MethodHandles.lookup()
创建了它,它封装了一个上下文,其中 class由您的新 class 加载程序定义的内容不可见。您可以通过 in(Class)
更改上下文,但这将更改访问模式并导致 LambdaMetaFactory
拒绝查找对象。在 Java 8 中,没有标准方法来创建对另一个 class.
仅出于演示目的,我们可以使用具有访问覆盖的反射来生成适当的查找对象,以表明它随后会起作用:
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);
Java9 引入的另一个选项是在您自己的包中生成 class 而不是新的 class 加载程序。然后,您自己的 class 查找上下文可以访问它:
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;
}
如果您继续使用自定义 class 加载器,您可以利用您正在生成代码这一事实。因此,您可以在生成的 class 中生成调用 MethodHandles.lookup()
的方法并返回它。然后,通过 Reflection 调用它,您将获得一个表示生成的 class 上下文的查找对象。另一方面,您也可以将生成 lambda 实例的指令直接插入生成的 class 本身:
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
指令。当然,这只是为了演示逻辑,因为很容易生成一个 class 实现直接在其函数方法中执行操作的接口,而不是要求元工厂生成一个委托 class .