我正在尝试使用在编译时不可用的类动态创建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
。
这会在运行时导致异常,因为它似乎使用目标方法ClassLoader
的{{1}}来加载lambda class
。这是stacktrace的有趣部分:
class
我该如何解决这个问题?有没有办法指定每个类使用哪个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
?是否存在另一种动态创建不会遇到此问题的lambda实例的方法?
任何帮助都非常感谢。
修改 这里有一个应该显示问题的小型可执行示例
1。主要课程
ClassLoader
2。生成的类
这假设该类已经编译为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);
}
}
文件,并且它位于系统类加载器范围之外的某个位置。
.class
对我来说,这导致以下stacktrace
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));
}
}
答案 0 :(得分:2)
我不知道你是如何创建类加载器的,但假设你已经有了类加载器,那么你可以替换
Class<?> targetClass = Class.forName(className);
与
Class<?> targetClass = yourClassLoader.loadClass(className);
答案 1 :(得分:2)
为什么选择您发布的路径并不完全清楚,特别是为什么它会在测试环境之外完全失败。但如果我理解正确,那么应该可以通过使用一个好的Dynamic Proxy Class来实现这个目标:
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
的位置。上面的代码只应被视为草图显示内容可能是解决方案的可行途径)