Подтвердить что ты не робот

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

Так как интерфейсы 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)
4b9b3361

Ответ 1

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

Example target = new Example();
...

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

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

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

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

Ответ 2

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

Object result = MethodHandles.lookup()
    .in(method.getDeclaringClass())
    .unreflectSpecial(method, method.getDeclaringClass())
    .bindTo(target)
    .invokeWithArguments();

Ответ 3

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

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