The crucial part is the MethodHandles.Lookup instance which defines the context the lambda will live in. Since you’ve created it via MethodHandles.lookup() in your main method, it encapsulates a context where the classes defined by your new class loader are not visible. You can change the context via in(Class) but this will change the access modes and cause the LambdaMetaFactory to reject the lookup object. In Java 8, there is no standard way to create a lookup object having private access to another class.
Just for demonstration purposes, we can use Reflection with access override to produce an appropriate lookup object, to show that it will work then:
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);
But, as we all know, Reflection with access override is discouraged, it will generate a warning in Java 9, and may break in future versions, as well as other JREs perhaps not even having this field.
On the other hand, Java 9 introduced a new way to get a lookup object, if the current module dependencies do not forbid it:
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);
Another option introduced by Java 9, is to generate a class into your own package instead of a new class loader. Then, it’s accessible to your own class lookup context:
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;
}
If you continue to use a custom class loader, you could utilize the fact that you are doing code generation anyway. So you could generate a method calling MethodHandles.lookup() in your generated class and returning it. Then, invoke it via Reflection and you have hands on a lookup object representing the context of the generated class. On the other hand, you could also insert the instruction to generate the lambda instance right into the generated class itself:
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());
}
This generates a second static method with the same name but no arguments, returning an instance of the functional interface, generated exactly like a method reference to the first static method, using a single invokedynamic instruction. Of course, that’s merely to demonstrate the logic, as it would be easy to generate a class implementing the interface performing the action within its function method directly, instead of requiring the meta factory to generate a delegating class.