Как явно вызвать метод по умолчанию из динамического прокси?

Поскольку интерфейсы Java 8 могут иметь методы по умолчанию. Я знаю, как явно вызывать метод из метода реализации, т. Е. (См.Явный вызов метода по умолчанию в Java)

Но как мнеэксплицитно вызвать метод по умолчанию, используя отражение, например, на прокси?

Пример:

interface ExampleMixin {

  String getText();

  default void printInfo(){
    System.out.println(getText());
  }
}

class Example {

  public static void main(String... args) throws Exception {

    Object target = new Object();

    Map<String, BiFunction<Object, Object[], Object>> behavior = new HashMap<>();

    ExampleMixin dynamic =
            (ExampleMixin) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{ExampleMixin.class}, (Object proxy, Method method, Object[] arguments) -> {

                //custom mixin behavior
                if(behavior.containsKey(method.getName())) {
                    return behavior.get(method.getName()).apply(target, arguments);
                //default mixin behavior
                } else if (method.isDefault()) {
                    //this block throws java.lang.IllegalAccessException: no private access for invokespecial
                    return MethodHandles.lookup()
                                        .in(method.getDeclaringClass())
                                        .unreflectSpecial(method, method.getDeclaringClass())
                                        .bindTo(target)
                                        .invokeWithArguments();
                //no mixin behavior
                } else if (ExampleMixin.class == method.getDeclaringClass()) {
                    throw new UnsupportedOperationException(method.getName() + " is not supported");
                //base class behavior
                } else{
                    return method.invoke(target, arguments);
                }
            });

    //define behavior for abstract method getText()
    behavior.put("getText", (o, a) -> o.toString() + " myText");

    System.out.println(dynamic.getClass());
    System.out.println(dynamic.toString());
    System.out.println(dynamic.getText());

    //print info should by default implementation
    dynamic.printInfo();
  }
}

Редактировать: Я знаю, что подобный вопрос был задан вКак вызвать методы по умолчанию Java 8 рефлексивно, но это не решило мою проблему по двум причинам:

проблема, описанная в этом вопросе, направлена ​​на то, чтобы вызвать ее через рефлексиюв общем - поэтому не было проведено никакого различия между методом по умолчанию и переопределенным методом - и это просто, вам нужен только экземпляр.один из ответов - использование дескрипторов методов - работает только с неприятным хаком (imho), таким как изменение модификаторов доступа к полям класса поиска, который относится к той же категории «решений», как этот:Изменить частное статическое конечное поле с помощью отражения Java: приятно знать, что это возможно, но я бы не стал использовать его в производстве - Я ищу "официальный" способ сделать это.

IllegalAccessException брошен вunreflectSpecial

Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface example.ExampleMixin, from example.ExampleMixin/package
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:852)
at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1568)
at java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1227)
at example.Example.lambda$main$0(Example.java:30)
at example.Example$Lambda$1/1342443276.invoke(Unknown Source)
 Gerald Mücke14 июн. 2016 г., 16:32
Это даже не так далеко, исключение брошено вunreflectedSpecialтак что в настоящее времяbindTo() может бытьnull, proxyАнонимный экземплярExampleMixin или что-нибудь еще
 Holger14 июн. 2016 г., 17:20
Но какой смысл доставлять «миксин» черезdefault методы интерфейса, когда вы в конечном итоге вызываете их все через Reflection?
 Gerald Mücke14 июн. 2016 г., 17:41
учитывая приведенный выше пример, я могу добавить поведение к моделям анемичных доменов, таким как сгенерированные JAXB, и обеспечить либо поведение по умолчанию (из интерфейсов), либо динамическое поведение (сменные функции, скрипты, что угодно - здесь речь не идет о безопасности :))
 Gerald Mücke14 июн. 2016 г., 16:21
Я соответственно обновил пример, хотел, чтобы он был максимально простым, но, надеюсь, теперь мои намерения проясняются
 Rudziankoŭ14 июн. 2016 г., 15:00
Разве это не дубликат этогоstackoverflow.com/questions/22614746/...
 Holger14 июн. 2016 г., 16:45
Понимаю. Проблема в том, чтоэтот пример работает только потому, что интерфейс является внутренним классом (что снижает полезность прокси)…
 Holger14 июн. 2016 г., 16:29
Цель вызова дляdefault метод должен быть примером этогоinterface, В вашем примере кода этоObject- как это должно работать?
 Gerald Mücke14 июн. 2016 г., 16:04
Как насчет миксинов - добавление функциональности в существующий класс с использованием динамического прокси? у меня есть экземпляр, и я хочу добавить дополнительные функциональные возможности, "добавив" интерфейсы с методами по умолчанию к экземпляру во время выполнения. Должен быть способ
 Pshemo14 июн. 2016 г., 15:58
"Я ищу "официальный" способ сделать это«Я могу ошибаться, но я боюсь, что официально вы не должны вызывать метод из супертипа, если ваш подтип переопределяет его. Допустим, ваш супертип имеетacceptSquare метод, который может принимать любые квадраты, но ваш подтипспециализация в обработке только красных квадратов, поэтому он переопределяет его соответственно, чтобы добавить тест на цвет (после этого он вызываетsuper.addSquare). Поэтому разрешение кому-либо вызывать реализацию из супертипа (даже через отражение) такого метода может быть большой дырой в безопасности.

Ответы на вопрос(4)

MethodHandle.Lookup в JDK 8 - 10, которые ведут себя по-разному.Я подробно рассказал о правильном решении здесь.

Этот подход работает в Java 8

В Java 8 идеальный подход использует хак, который обращается к конструктору частного пакета изLookup:

import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;

interface Duck {
    default void quack() {
        System.out.println("Quack");
    }
}

public class ProxyDemo {
    public static void main(String[] a) {
        Duck duck = (Duck) Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] { Duck.class },
            (proxy, method, args) -> {
                Constructor<Lookup> constructor = Lookup.class
                    .getDeclaredConstructor(Class.class);
                constructor.setAccessible(true);
                constructor.newInstance(Duck.class)
                    .in(Duck.class)
                    .unreflectSpecial(method, Duck.class)
                    .bindTo(proxy)
                    .invokeWithArguments();
                return null;
            }
        );

        duck.quack();
    }
}

Это единственный подход, который работает как с частно-доступными, так и с приватно-недоступными интерфейсами. Тем не менее, вышеупомянутый подход делает незаконный рефлексивный доступ к внутренним компонентам JDK, который больше не будет работать в будущей версии JDK, или если--illegal-access=deny указан на JVM.

Этот подход работает на Java 9 и 10, но не 8
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Proxy;

interface Duck {
    default void quack() {
        System.out.println("Quack");
    }
}

public class ProxyDemo {
    public static void main(String[] a) {
        Duck duck = (Duck) Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] { Duck.class },
            (proxy, method, args) -> {
                MethodHandles.lookup()
                    .findSpecial( 
                         Duck.class, 
                         "quack",  
                         MethodType.methodType(void.class, new Class[0]),  
                         Duck.class)
                    .bindTo(proxy)
                    .invokeWithArguments();
                return null;
            }
        );

        duck.quack();
    }
}
Решение

Просто реализуйте оба вышеупомянутых решения и проверьте, работает ли ваш код на JDK 8 или на более позднем JDK, и все будет в порядке. Пока тебя нет :)

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

Object target = Proxy.newProxyInstance(classLoader,
      new Class[]{exampleInterface}, (Object p, Method m, Object[] a) -> null);

Создайте экземпляр интерфейса, а затем создайте MethodHandles.Lookup, используя отражение:

Constructor<MethodHandles.Lookup> lookupConstructor = 
    MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
if (!lookupConstructor.isAccessible()) {
    lookupConstructor.setAccessible(true);
}

И затем использовать этоlookupConstructor создать новый экземпляр вашего интерфейса, который позволит частный доступ кinvokespecial, Затем вызовите метод на поддельном проксиtarget ты сделал раньше.

lookupConstructor.newInstance(exampleInterface,
        MethodHandles.Lookup.PRIVATE)
        .unreflectSpecial(method, declaringClass)
        .bindTo(target)
        .invokeWithArguments(args);
 user113594014 янв. 2018 г., 15:12
MethodHandles.Lookup предоставляет «незаконный отражающий доступ», такой же, как указано выше.
Решение Вопроса

ывающей стороны для invokeSpecial, он должен правильно вызывать реализацию интерфейса по умолчанию (не требуется взлом для частного доступа):

Example target = new Example();
...

Class targetClass = target.getClass();
return MethodHandles.lookup()
                    .in(targetClass)
                    .unreflectSpecial(method, targetClass)
                    .bindTo(target)
                    .invokeWithArguments();

Это, конечно, работает, только если у вас есть ссылка на конкретный объект, реализующий интерфейс.

Редактировать: это решение будет работать только в том случае, если рассматриваемый класс (пример в приведенном выше коде) закрыт для доступа из кода вызывающей стороны, например анонимный внутренний класс.

Текущая реализация класса MethodHandles / Lookup не позволяет вызывать invokeSpecial для любого класса, который не является закрытым для текущего класса вызывающих. Существуют различные обходные пути, но все они требуют использования отражения, чтобы сделать конструкторы / методы доступными, что, вероятно, приведет к сбою в случае установки SecurityManager.

 iirekm31 янв. 2017 г., 16:00
java.lang.IllegalAccessException: нет личного доступа для invokespecial
 T. Neidhart14 июн. 2016 г., 19:15
@Holger: Я полагаю, вы правы после проверки исходного кода Java 8 для MethodHandles. Таким образом, не будет никакого обходного пути, который не вовлекает некоторые взломы отражения?
 Lukas Eder25 июл. 2016 г., 13:57
Хм, это работает, только если интерфейс хорошо известен и может быть реализован в исходном коде. Если интерфейс простоClassне сработает
 krevelen03 окт. 2016 г., 21:00
Решение с использованием отражения было предоставлено месяцем ранее в мае 2014 года вэтот комментарий к блогу и применяется врасширение поддержки Java8 ВЛАДЕЛЕЦ API, а также
 Holger14 июн. 2016 г., 19:18
Когда дело доходит до произвольных интерфейсов, не известных во время компиляции, кажется, что без взлома не может быть решения.
 Gerald Mücke14 июн. 2016 г., 17:43
хм, это также работает с анонимными классами ... но учитывая, что я не знаю интерфейсов раньше (то есть, потому что это параметр), могу ли я создавать экземпляры анонимных классов динамически? (кажется, что прокси не работает, генерация байтового кода тоже не подходит)
 T. Neidhart14 июн. 2016 г., 18:35
Это было ошибкой, бывшая действительно должна быть заменена на цель.
 Holger14 июн. 2016 г., 18:42
@Gerald Mücke: нет смысла пытаться создать реализацию, независимо от того, какой метод вы используете. Когда вы вызываетеMethodHandles.lookup()вы получаете контекст, разрешающий частный доступ к себе. Как только вы вызываетеin(someOtherClass) на нем с классом, который не имеет отношения внутреннего класса к себе, который включает в себя все виды сгенерированных классов, вы потеряете свойство частного доступа, которое требуется дляunreflectSpecial.
 iirekm31 янв. 2017 г., 16:09
правильное решение здесь:zeroturnaround.com/rebellabs/...
 Holger14 июн. 2016 г., 18:33
Что такоеex в этом примере? Это должно быть примеромExample здесь, так почему бы просто не использоватьtarget пример? Кроме того, он имеет то же ограничение, что и использованиеExampleMixin непосредственно; это работает, только если указанный класс имеет внутреннюю связь классов с окружающим кодом (кодом, который вызываетlookup()).

Использование:

Object result = MethodHandles.lookup()
    .in(method.getDeclaringClass())
    .unreflectSpecial(method, method.getDeclaringClass())
    .bindTo(target)
    .invokeWithArguments();
 Gerald Mücke14 июн. 2016 г., 14:52
Я попробовал это, дал мнеCaused by: java.lang.IllegalAccessException: no private access for invokespecial: interface example.IExample, from example.IExample/package

Ваш ответ на вопрос