Java 使用LambdaMetafactory创建CallSite时,我自己的类的NoClassDefFoundError

Java 使用LambdaMetafactory创建CallSite时,我自己的类的NoClassDefFoundError,java,noclassdeffounderror,lambda-metafactory,Java,Noclassdeffounderror,Lambda Metafactory,我正试图创建一个小实用程序来代替我在整个项目中使用反射,主要是为了使用LambdaMetafactory的性能优势,但是我在创建调用站点时遇到了障碍。但是,只有在访问非我自己的类时,问题才会出现。例如,访问第三方库甚至Java自己的类Java.lang.Object将导致NoClassDefFoundError,而不是第三方类,而是我的接口 public final class Accessor { private static Constructor<MethodHandles

我正试图创建一个小实用程序来代替我在整个项目中使用反射,主要是为了使用LambdaMetafactory的性能优势,但是我在创建调用站点时遇到了障碍。但是,只有在访问非我自己的类时,问题才会出现。例如,访问第三方库甚至Java自己的类Java.lang.Object将导致NoClassDefFoundError,而不是第三方类,而是我的接口

public final class Accessor {

    private static Constructor<MethodHandles.Lookup> lookupConstructor;

    static {
        newLookupConstructor();
    }

    protected static void newLookupConstructor() {
        try {
            lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
            lookupConstructor.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("MethodHandles.Lookup class constructor (Class) not found! Check java version.");
        }
    }

    private Accessor() { }

    public static <T> T to(Class<T> interfaze, Class<?> clazz, String method, Class<?>... params) {
        try {
            return to(interfaze, clazz.getDeclaredMethod(method, params));
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }

    public static <T> T to(Class<T> interfaze, Method method) {
        try {
            MethodHandles.Lookup caller = lookupConstructor.newInstance(method.getDeclaringClass());
            MethodHandle implMethod = caller.unreflect(method);
            CallSite site = LambdaMetafactory.metafactory(caller, method.getName(), MethodType.methodType(interfaze), implMethod.type(), implMethod, implMethod.type());
            // ^ java.lang.NoClassDefFoundError for the passed interfaze class
            return (T) site.getTarget().invoke();
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
这将引发以下问题:

java.lang.NoClassDefFoundError:com/gmail/justisroot/autoecon/data/AccessorTest$ToString 在java.base/jdk.internal.misc.Unsafe.defineAnonymousClass0Native方法中 位于java.base/jdk.internal.misc.Unsafe.defineAnonymousClassUnsafe.java:1223 java:320 位于java.base/java.lang.invoke.InnerClassLambdaMetafactory.buildCallSiteInnerClassLambdaMetafactory.java:188 位于java.base/java.lang.invoke.LambdaMetafactory.metafactoryLambdaMetafactory.java:317 在com.gmail.justisroot.autoecon.data.Accessor.toAccessor.java上:43

我花了太多的时间绞尽脑汁想办法。第二双眼睛肯定会有帮助

我做错了什么?

您使用的是表示方法对象的声明类的查找对象。因此,当目标方法为Object.class.getDeclaredMethodtoString时,您正在为java.lang.Object创建一个查找对象,该对象由引导加载程序加载

因此,您只能访问引导加载程序已知的类,这就排除了您自己的ToString接口

public final class Accessor {

    private static Constructor<MethodHandles.Lookup> lookupConstructor;

    static {
        newLookupConstructor();
    }

    protected static void newLookupConstructor() {
        try {
            lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
            lookupConstructor.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("MethodHandles.Lookup class constructor (Class) not found! Check java version.");
        }
    }

    private Accessor() { }

    public static <T> T to(Class<T> interfaze, Class<?> clazz, String method, Class<?>... params) {
        try {
            return to(interfaze, clazz.getDeclaredMethod(method, params));
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }

    public static <T> T to(Class<T> interfaze, Method method) {
        try {
            MethodHandles.Lookup caller = lookupConstructor.newInstance(method.getDeclaringClass());
            MethodHandle implMethod = caller.unreflect(method);
            CallSite site = LambdaMetafactory.metafactory(caller, method.getName(), MethodType.methodType(interfaze), implMethod.type(), implMethod, implMethod.type());
            // ^ java.lang.NoClassDefFoundError for the passed interfaze class
            return (T) site.getTarget().invoke();
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
通常,在组合任意接口和目标方法时,必须找到一个同时知道这两者的类装入器。OpenJDK中的底层生成器工具不需要这样做,但LambdaMetafactory强制执行这一点

类似地,它强制lookup对象必须具有对lookup类的私有访问权限,即使该类在其他方面不相关,例如仅访问公共工件时也是如此。这就是MethodHandles.publicLookup不起作用的原因

但是当接口和目标方法都可以从当前代码的类加载器访问时,MethodHandles.lookup应该可以工作,而不需要侵入内部。

您使用的是表示方法对象声明类的lookup对象。因此,当目标方法为Object.class.getDeclaredMethodtoString时,您正在为java.lang.Object创建一个查找对象,该对象由引导加载程序加载

因此,您只能访问引导加载程序已知的类,这就排除了您自己的ToString接口

public final class Accessor {

    private static Constructor<MethodHandles.Lookup> lookupConstructor;

    static {
        newLookupConstructor();
    }

    protected static void newLookupConstructor() {
        try {
            lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
            lookupConstructor.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("MethodHandles.Lookup class constructor (Class) not found! Check java version.");
        }
    }

    private Accessor() { }

    public static <T> T to(Class<T> interfaze, Class<?> clazz, String method, Class<?>... params) {
        try {
            return to(interfaze, clazz.getDeclaredMethod(method, params));
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }

    public static <T> T to(Class<T> interfaze, Method method) {
        try {
            MethodHandles.Lookup caller = lookupConstructor.newInstance(method.getDeclaringClass());
            MethodHandle implMethod = caller.unreflect(method);
            CallSite site = LambdaMetafactory.metafactory(caller, method.getName(), MethodType.methodType(interfaze), implMethod.type(), implMethod, implMethod.type());
            // ^ java.lang.NoClassDefFoundError for the passed interfaze class
            return (T) site.getTarget().invoke();
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
通常,在组合任意接口和目标方法时,必须找到一个同时知道这两者的类装入器。OpenJDK中的底层生成器工具不需要这样做,但LambdaMetafactory强制执行这一点

类似地,它强制lookup对象必须具有对lookup类的私有访问权限,即使该类在其他方面不相关,例如仅访问公共工件时也是如此。这就是MethodHandles.publicLookup不起作用的原因


但是当接口和目标方法都可以从当前代码的类加载器访问时,MethodHandles.lookup应该可以工作,而不需要侵入内部。

侵入lookup构造函数的原因是什么?如果你想访问私有方法,为什么不使用method.setAccessibletrue+unreflectmethod呢?当我试图访问Java类或其他第三方类时,即使对于公共类中的公共方法,我也会得到一个非法的AccessException。授予查找完全\u POWER \u模式的权限解决了此问题。您侵入查找构造函数的原因是什么?如果你想访问私有方法,为什么不使用method.setAccessibletrue+unreflectmethod呢?当我试图访问Java类或其他第三方类时,即使对于公共类中的公共方法,我也会得到一个非法的AccessException。授予查找全功率模式的权限解决了此问题。因此,如果我理解正确,为了避免NCDFE,我需要以某种方式使目标方法的类加载器可以访问我的接口?你对我如何实现这一点有什么想法吗?你已经有了一个类加载器,可以同时访问这两个类。在您的示例中,Object.toString可以从toString的类加载器访问,因为引导加载器是它的祖辈。因此,当您为ToString创建查找对象时,它将起作用。由于这也是测试代码的类加载器,因此在此设置中仅使用MethodHandles.lookup也可以。接口和目标方法的声明类具有相同的loader或loade并不罕见
有父母关系的人。真正棘手的是两个没有这种关系的加载程序。因此,如果我理解正确,为了避免NCDFE,我需要以某种方式使目标方法的类加载程序可以访问我的接口?你对我如何实现这一点有什么想法吗?你已经有了一个类加载器,可以同时访问这两个类。在您的示例中,Object.toString可以从toString的类加载器访问,因为引导加载器是它的祖辈。因此,当您为ToString创建查找对象时,它将起作用。由于这也是测试代码的类加载器,因此在此设置中仅使用MethodHandles.lookup也可以。接口和目标方法的声明类具有相同的一个或多个具有父关系的加载程序,这并不奇怪。真正棘手的是两个没有这种关系的装载机。