Рубрики
Без рубрики

Добавление поддержки реализаций Java InvocationHandler для методов интерфейса по умолчанию

Java 8 ввела методы по умолчанию для интерфейсов. Существующие реализации InvocationHandler этого не сделают… Помеченный java.

Java 8 ввела методы по умолчанию для интерфейсов. Существующий Реализации InvocationHandler не будут вызывать методы интерфейса по умолчанию. В этой короткой статье документируются необходимые изменения. Примечание: Сначала в нем описывается реализация, основанная на чтении документов, а затем предоставляется рабочая реализация для Java 8.

Учитывая Обработчик вызовов реализация:

    @Override
    public Object invoke(Object proxy,
                         Method method, Object[] argv) throws Throwable {
        Object result = null;
        /*
         * Logic to calculate result.
         */
        return result;
    }

решение Java 8 , по-видимому, заключается в расширении реализации для вызова любых методов интерфейса по умолчанию через MethodHandle :

    @Override
    public Object invoke(Object proxy,
                         Method method, Object[] argv) throws Throwable {
        Object result = null;
        Class declaringClass = method.getDeclaringClass();

        if (method.isDefault()) {
            result =
                MethodHandles.lookup()
                .in(declaringClass)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(argv);
        } else {
            /*
             * Logic to calculate result.
             */
        }

        return result;
    }

Если Метод имеет значение “по умолчанию”, то вызывается метод целевого интерфейса. В противном случае реализация InvocationHandler выполняется, как и раньше. Любой метод интерфейса по умолчанию должен вызываться:

  1. Нахождение дескрипторов метода . Поиск через MethodHandles.lookup().in(объявление класса) ,
  2. Получить MethodHandle обход любых переопределяющих методов через .unreflectSpecial(метод, объявляющий класс) и,
  3. Вызовите метод на прокси-сервере с помощью .bind To(proxy).invokeWithArguments(argv)

К сожалению, это не работает, если объявляющий класс не является “приватно доступным” для вызывающего (что происходит большую часть времени), что приводит к:

Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface package1.SomeInterface, from package1.SomeInterface/public
    at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850)
    at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572)
    at java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1231)
    at package2.InvocationHandlerImpl.invoke(InvocationHandlerImpl.java:59)

Фактическое решение Java 8 является:

    @Override
    public Object invoke(Object proxy,
                         Method method, Object[] argv) throws Throwable {
        Object result = null;
        Class declaringClass = method.getDeclaringClass();

        if (method.isDefault()) {
            Constructor constructor =
                MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);

            constructor.setAccessible(true);

            result =
                constructor.newInstance(declaringClass)
                .in(declaringClass)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(argv);
        } else {
            /*
             * Logic to calculate result.
             */
        }

        return result;
    }

Это не будет работать в Java 9 +. В Java 9 и последующих версиях решение должно основываться на дескрипторах методов . Lookup.findSpecial() и/или Метод Обрабатывает.privateLookupIn() .

Оригинал: “https://dev.to/allenball/adding-support-to-java-invocationhandler-implementations-for-interface-default-methods-h64”