1

我正在尝试创建一个小型实用程序来代替我在整个项目中使用反射(主要是为了使用 LambdaMetafactory 的性能优势),但我在创建 CallSite 时遇到了麻烦。但是,仅在访问不是我自己的类时才会出现此问题。访问 3rd 方库甚至 Java 自己的类(例如 java.lang.Object)将导致 NoClassDefFoundError 不是针对 3rd 方类,而是针对我的接口。

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

我运行的单元测试证明了这个问题可以在这里找到:

final class AccessorTest {

    @Test // SUCCESS
    @DisplayName("Verify MethodHandles.Lookup constructor")
    void lookupConstructorAvailabilityTest() {
        Assertions.assertDoesNotThrow(() -> Accessor.newLookupConstructor());
    }

    @Test // SUCCESS
    @DisplayName("Verify available matching instance method is called")
    void findMatchingMethodAndCallTest() {
        ObjectAccessor accessor = Accessor.to(ObjectAccessor.class, TestObject.class, "instanceMethod");
        Assertions.assertNotNull(accessor);
        Assertions.assertTrue(accessor.instanceMethod(new TestObject()));
    }

    @Test // SUCCESS
    @DisplayName("Verify available matching static method is called")
    void findMatchingStaticMethodAndCallTest() {
        ObjectAccessor accessor = Accessor.to(ObjectAccessor.class, TestObject.class, "staticMethod");
        Assertions.assertNotNull(accessor);
        Assertions.assertTrue(accessor.staticMethod());
    }

    @Test // FAILURE
    @DisplayName("Verify java.lang.Object#toString works")
    void testDynamicToStringInvokation() {
        ToString accessor = Accessor.to(ToString.class, Object.class, "toString");
        // ^ java.lang.NoClassDefFoundError: com/gmail/justisroot/autoecon/data/AccessorTest$ToString
        Assertions.assertNotNull(accessor);
        Assertions.assertEquals(accessor.toString(Integer.valueOf(42)), "42");
    }

    public interface ObjectAccessor {
        public boolean instanceMethod(TestObject o);
        public boolean staticMethod();
    }

    public interface ToString {
        public String toString(Object o);
    }
}

这将抛出以下内容:

java.lang.NoClassDefFoundError: com/gmail/justisroot/autoecon/data/AccessorTest$ToString at java.base/jdk.internal.misc.Unsafe.defineAnonymousClass0(Native Method) at java.base/jdk.internal.misc.Unsafe。在 java.base/java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:188) 的 java.base/java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:320) 的 defineAnonymousClass(Unsafe.java:1223)在 java.base/java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:317) 在 com.gmail.justisroot.autoecon.data.Accessor.to(Accessor.java:43)

我已经花了太多时间绞尽脑汁寻找解决方案。第二双眼睛肯定会有所帮助。

我究竟做错了什么?

4

1 回答 1

3

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

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

通常,当组合任意接口和目标方法时,您必须找到一个知道两者的类加载器。OpenJDK 中的底层生成器工具不需要这样做,但会LambdaMetafactory强制执行此操作。

同样,它强制查找对象必须有权private访问查找类,即使该类在其他方面不相关,例如仅访问public工件时。这就是为什么MethodHandles.publicLookup()不起作用。

但是,当interface从当前代码的类加载器中可以访问 the 和 target 方法时,MethodHandles.lookup()应该可以工作,而无需侵入内部。

于 2020-02-10T19:07:36.303 回答