使用不同的类加载器在Java中动态创建lambda

Mar*_*inS 11 java lambda

我正在尝试使用在编译时不可用的类动态创建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());
}
Run Code Online (Sandbox Code Playgroud)

潜在的电话可能看起来像这样

@FunctionalInterface
interface MyLambda {
  double call(double d);
}

public void foo() {
  lookupLambda(MyLambda.class, "java.lang.Math", "sin", double.class, double.class);
}
Run Code Online (Sandbox Code Playgroud)

在实验设置中,这很有效.然而,在实际代码中,lambda class使用ClassLoader与应用程序的其余部分(即class目标方法)不同的方式加载.这会在运行时导致异常,因为它似乎使用ClassLoader目标方法class来加载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
Run Code Online (Sandbox Code Playgroud)

我怎样才能解决这个问题?有没有办法指定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);
  }

}
Run Code Online (Sandbox Code Playgroud)

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));
  }

} 
Run Code Online (Sandbox Code Playgroud)

对我来说,这导致以下堆栈跟踪

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
Run Code Online (Sandbox Code Playgroud)

Tho*_*ger 2

我不知道你如何创建类加载器,但假设你已经有一个类加载器,那么你可以替换

Class<?> targetClass = Class.forName(className);
Run Code Online (Sandbox Code Playgroud)

Class<?> targetClass = yourClassLoader.loadClass(className);
Run Code Online (Sandbox Code Playgroud)

  • 该类已经加载,但似乎“sun.misc.Unsafe.defineAnonymousClass”正在尝试使用另一个“ClassLoader”再次加载它,然后失败。 (3认同)