[英]Dynamically create lambda in Java using different classloaders
我正在嘗試使用在編譯時不可用的類動態創建lambda實例,因為它們是在運行時生成的,或者在編譯時尚不知道。
這可以使用以下代碼
// lambdaClass = class of the lambda interface
// className = class containing the target method
// methodName = name of the target method
private static <T> T lookupLambda(Class<T> lambdaClass, String className, String methodName, Class<?> returnType,
Class<?> argumentType) throws Throwable {
MethodType lambdaType = MethodType.methodType(lambdaClass);
MethodType methodType = MethodType.methodType(returnType, argumentType);
MethodHandles.Lookup lookup = MethodHandles.lookup();
Class<?> targetClass = Class.forName(className);
MethodHandle handle = lookup.findStatic(targetClass, methodName, methodType);
CallSite callSite = LambdaMetafactory
.metafactory(lookup, "call", lambdaType, methodType.unwrap(), handle, methodType);
MethodHandle methodHandle = callSite.getTarget();
return lambdaClass.cast(methodHandle.invoke());
}
潛在的電話可能看起來像這樣
@FunctionalInterface
interface MyLambda {
double call(double d);
}
public void foo() {
lookupLambda(MyLambda.class, "java.lang.Math", "sin", double.class, double.class);
}
在實驗設置中,這很有效。 但是在實際代碼中,lambda class
使用與應用程序其余部分不同的ClassLoader
,即目標方法的class
。 這會在運行時導致異常,因為它似乎使用目標方法class
的ClassLoader
來加載lambda class
。 這是stacktrace的有趣部分:
Caused by: java.lang.NoClassDefFoundError: GeneratedPackage.GeneratedClass$GeneratedInterface
at sun.misc.Unsafe.defineAnonymousClass(Native Method)
at java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:326)
at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:194)
at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:304)
at my.project.MyClass.lookupLambda(MyClass.java:765)
at
... 9 more
Caused by: java.lang.ClassNotFoundException: GeneratedPackage.GeneratedClass$GeneratedInterface
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 15 more
我怎樣才能解決這個問題? 有沒有辦法指定每個類使用哪個ClassLoader
? 是否存在另一種動態創建不會遇到此問題的lambda實例的方法? 任何幫助都非常感謝。
編輯:這是一個應該顯示問題的小型可執行示例
1.主要班級
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class Test {
private static <T> T lookupLambda(Class<T> lambdaClass, String className, String methodName, Class<?> returnType,
Class<?> argumentType) throws Throwable {
MethodType lambdaType = MethodType.methodType(lambdaClass);
MethodType methodType = MethodType.methodType(returnType, argumentType);
MethodHandles.Lookup lookup = MethodHandles.lookup();
Class<?> targetClass = Class.forName(className);
MethodHandle handle = lookup.findStatic(targetClass, methodName, methodType);
CallSite callSite = LambdaMetafactory
.metafactory(lookup, "call", lambdaType, methodType.unwrap(), handle, methodType);
MethodHandle methodHandle = callSite.getTarget();
return lambdaClass.cast(methodHandle.invoke());
}
public static void main(String[] args) throws Throwable {
URL resourcesUrl = new URL("file:/home/pathToGeneratedClassFile/");
ClassLoader classLoader = new URLClassLoader(new URL[] { resourcesUrl }, Thread.currentThread().getContextClassLoader());
Class<?> generatedClass = classLoader.loadClass("GeneratedClass");
Class<?> generatedLambdaClass = classLoader.loadClass("GeneratedClass$GeneratedLambda");
Constructor constructor = generatedClass.getConstructor(generatedLambdaClass);
Object instance = constructor
.newInstance(lookupLambda(generatedLambdaClass, "java.lang.Math", "sin", double.class, double.class));
Method method = generatedClass.getDeclaredMethod("test");
method.invoke(instance);
}
}
2.生成的類假定該類已經編譯為.class
文件,並且它位於系統類加載器范圍之外的某個位置。
import javax.annotation.Generated;
@Generated("This class is generated and loaded using a different classloader")
public final class GeneratedClass {
@FunctionalInterface
public interface GeneratedLambda {
double call(double d);
}
private final GeneratedLambda lambda;
public GeneratedClass(GeneratedLambda lambda) {
this.lambda = lambda;
}
public void test() {
System.out.println(lambda.call(3));
}
}
對我來說,這導致以下堆棧跟蹤
Exception in thread "main" java.lang.NoClassDefFoundError: GeneratedClass$GeneratedLambda
at sun.misc.Unsafe.defineAnonymousClass(Native Method)
at java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:326)
at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:194)
at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:304)
at Test.lookupLambda(Test.java:21)
at Test.main(Test.java:36)
Caused by: java.lang.ClassNotFoundException: GeneratedClass$GeneratedLambda
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 6 more
我不知道你是如何創建你的類加載器的,但假設你已經有了一個,那么你可以替換它
Class<?> targetClass = Class.forName(className);
同
Class<?> targetClass = yourClassLoader.loadClass(className);
現在還不完全清楚為什么選擇你發布的路徑,特別是為什么它會在測試環境之外失敗。 但如果我理解正確,那么應該可以通過使用一個好的舊動態代理類來實現這個目標:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@FunctionalInterface
interface MyLambda
{
double call(double d);
}
public class DynamicLambdaTest
{
public static void main(String[] args) throws Throwable
{
MyLambda x = lookupLambda(
MyLambda.class, "java.lang.Math", "sin",
double.class, double.class);
System.out.println(x.call(Math.toRadians(45)));
}
private static <T> T lookupLambda(Class<T> lambdaClass, String className,
String methodName, Class<?> returnType, Class<?> argumentType)
throws Throwable
{
Object proxy = Proxy.newProxyInstance(lambdaClass.getClassLoader(),
new Class[] { lambdaClass },
new LambdaProxy(lambdaClass, className, methodName, argumentType));
@SuppressWarnings("unchecked")
T lambda = (T)proxy;
return (T)lambda;
}
}
class LambdaProxy implements InvocationHandler {
// The object method handling is based on
// docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
private static Method hashCodeMethod;
private static Method equalsMethod;
private static Method toStringMethod;
static
{
try
{
hashCodeMethod =
Object.class.getMethod("hashCode", (Class<?>[]) null);
equalsMethod =
Object.class.getMethod("equals", new Class[] { Object.class });
toStringMethod =
Object.class.getMethod("toString", (Class<?>[]) null);
}
catch (NoSuchMethodException e)
{
throw new NoSuchMethodError(e.getMessage());
}
}
private Class<?> lambdaClass;
private Method callMethod;
public LambdaProxy(Class<?> lambdaClass, String className,
String methodName, Class<?> argumentType) {
this.lambdaClass = lambdaClass;
try
{
Class<?> c = Class.forName(className);
this.callMethod = c.getDeclaredMethod(methodName, argumentType);
}
catch (ClassNotFoundException
| NoSuchMethodException
| SecurityException e)
{
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable
{
Class<?> declaringClass = m.getDeclaringClass();
if (declaringClass == Object.class)
{
if (m.equals(hashCodeMethod))
{
return proxyHashCode(proxy);
}
else if (m.equals(equalsMethod))
{
return proxyEquals(proxy, args[0]);
}
else if (m.equals(toStringMethod))
{
return proxyToString(proxy);
}
else
{
throw new InternalError(
"unexpected Object method dispatched: " + m);
}
}
if (declaringClass == lambdaClass)
{
return callMethod.invoke(null, args);
}
throw new Exception("Whoopsie");
}
private int proxyHashCode(Object proxy) {
return System.identityHashCode(proxy);
}
private boolean proxyEquals(Object proxy, Object other) {
return (proxy == other);
}
private String proxyToString(Object proxy) {
return proxy.getClass().getName() + '@' +
Integer.toHexString(proxy.hashCode());
}
}
(您甚至可以將調用處理程序中callMethod
的初始化推遲到第一次invoke
位置。上面的代碼應該只被視為草圖,顯示可能是解決方案的可行路徑)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.