簡體   English   中英

使用不同的類加載器在Java中動態創建lambda

[英]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 這會在運行時導致異常,因為它似乎使用目標方法classClassLoader來加載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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM