I'm using the stuff of java.lang.invoke to create lambdas out of methods obtained by reflection. When the method is called frequently, it improves performances.
It works quite well, until I discovered that it no longer works when the method comes from a class loaded by a ClassLoader different than the default one.
What have I missed ? What do I need to change in order to make it work ?
IN case there is an importance, I'm on Java 17.
First of all, the class I'm going to load externally:
public class TestFunc {
public static String testFunc (String str) {
return str.toUpperCase();
}
}
Then, the main example code:
import java.lang.reflect.*;
import java.lang.invoke.*;
import java.util.*;
import java.util.function.*;
import java.net.*;
import java.io.*;
public class TestLambda {
public static <I> I createLambda (Object obj, Executable method, Class<I> interfaceClass, Class<?> returnType, String name, Class<?>... mArgs) throws ReflectiveOperationException {
try {
method.setAccessible(true);
boolean isConstructor = method instanceof Constructor, isStatic = isConstructor || Modifier.isStatic(method.getModifiers()), isBound = obj!=null&&!isStatic;
var cls = method.getDeclaringClass();
var pt = method.getParameterTypes();
var rt = isConstructor? cls : ((Method)method) .getReturnType();
var lookup = MethodHandles.lookup();
if (Modifier.isPrivate(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) lookup = MethodHandles.privateLookupIn(cls, lookup);
var mh = isConstructor? lookup.unreflectConstructor((Constructor)method) : lookup.unreflect((Method)method);
var interfaceMT = isBound? MethodType.methodType(interfaceClass, cls) : MethodType.methodType(interfaceClass);
var implementationMT = MethodType.methodType(returnType, mArgs);
var calledMT = isStatic || isBound? MethodType.methodType(rt, pt) : MethodType.methodType(rt, cls, pt);
var callSite = LambdaMetafactory.metafactory(lookup, name, interfaceMT, implementationMT, mh, calledMT);
return (I) (isBound?
callSite.getTarget() .invoke(obj) :
callSite.getTarget() .invoke() );
} catch (Throwable t) {
throw new ReflectiveOperationException(t);
}
}
public static <T,R> Function<T,R> createFunction (Object obj, Executable method, Class<T> argType, Class<R> returnType) throws ReflectiveOperationException {
return (Function<T,R>) createLambda(obj, method, Function.class, Object.class, "apply", Object.class);
}
public static void main (String[] args) throws Throwable {
// Example usage with a trivial case where it works well
Method toUpperCaseMethod = String.class.getMethod("toUpperCase");
Function<String, String> toUpperCaseFunction = createFunction(null, toUpperCaseMethod, String.class, String.class);
System.out.println(toUpperCaseFunction.apply("Hello, world!"));
// Example 2 to show that calling the external method via standars reflection API works as well
ClassLoader classLoader = new URLClassLoader( new URL[]{ new File("test.jar").toURL() }, TestLambda.class.getClassLoader());
Class theClass = classLoader.loadClass("TestFunc");
Method method = theClass.getMethod("testFunc", String.class);
System.out.println(method.invoke(null, "This is working"));
// Example 3 where it doesn't work
Function<String,String> func = createFunction(null, method, String.class, String.class);
System.out.println(func.apply("This doesn't work"));
}
}
Result :
HELLO, WORLD!
THIS IS WORKING
Exception in thread "main" java.lang.NoClassDefFoundError: TestFunc
at TestLambda.main(TestLambda.java:51)
Caused by: java.lang.ClassNotFoundException: TestFunc
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
at jdk.compiler/com.sun.tools.javac.launcher.Main$MemoryClassLoader.loadClass(Main.java:600)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
at TestLambda.main(TestLambda.java:51)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:419)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)