NoClassDefFoundError для моего собственного класса при создании CallSite с LambdaMetafactory - PullRequest
1 голос
/ 10 февраля 2020

Я пытаюсь создать небольшую утилиту, чтобы заменить использование отражений во всем проекте (в основном для повышения производительности при использовании LambdaMetafactory), но я спотыкаюсь при создании CallSite. Однако эта проблема возникает только при доступе к классам, которые мне не принадлежат. Доступ к сторонним библиотекам или даже к собственным классам 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;
        }
    }
}

Юнит-тесты I ' Вы можете запустить демонстрацию проблемы здесь:

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 в java .base / jdk.internal.mis c .Unsafe.defineAnonymousClass0 (собственный метод) в java .base / jdk.internal.mis c .Unsafe.defineAnonymousClass (небезопасно. java: 1223) в java .base / java .lang.invoke.InnerClassLambdaMetafactory.spinInnerClass (InnerClassLambdaMetafactory. java: 320) в java .base / java .lang.invoke.Innerelassamb. buildCallSite (InnerClassLambdaMetafactory. java: 188) в java .base / java .lang.invoke.LambdaMetafactory.metafactory (LambdaMetafactory. java: 317) в com.gmail.justis root .autocon .Accessor.to (Accessor. java: 43)

Я слишком много времени тратил на то, чтобы ломать голову над решениями. Вторая пара глаз наверняка будет полезна.

Что я делаю не так?

1 Ответ

3 голосов
/ 10 февраля 2020

Вы используете объект Lookup, представляющий декларирующий класс объекта Method. Поэтому, когда целевым методом является Object.class.getDeclaredMethod("toString"), вы создаете объект поиска для java.lang.Object, который загружается загрузчиком bootstrap.

Как следствие, вы можете получить доступ только к классам, известным для bootstrap загрузчик, который исключает ваш собственный ToString интерфейс.

Как правило, при объединении произвольных интерфейсов и целевых методов вы должны найти загрузчик классов, который знает оба. Этого не потребует базовая функция генератора в OpenJDK, но LambdaMetafactory обеспечивает это.

Аналогично, он обеспечивает, что объект поиска должен иметь private доступ к классу поиска, даже когда в противном случае класс не имеет значения, например, при доступе только к public артефактам. Вот почему MethodHandles.publicLookup() не работает.

Но когда оба, interface и целевой метод, доступны из загрузчика классов вашего текущего кода, MethodHandles.lookup() должен работать без необходимости взлома во внутренние органы.

...