Имея динамический прокси для интерфейса с методами по умолчанию, как мне вызвать метод по умолчанию? Используя что-то вроде defaultmethod.invoke(this, ...), вы просто вызываете обработчик вызова прокси-сервера (что в некотором роде правильно, потому что у вас нет класса реализации для этого интерфейса).
У меня есть обходной путь, использующий ASM для создания класса, реализующего интерфейс и делегирующего такие вызовы экземпляру этого класса. Но это не очень хорошее решение, особенно если метод по умолчанию вызывает другие методы интерфейса (вы получаете делегаторный пинг-понг). JLS на удивление молчит по этому вопросу...
Вот небольшой пример кода:
public class Java8Proxy implements InvocationHandler {
public interface WithDefaultMethod {
void someMethod();
default void someDefaultMethod() {
System.out.println("default method invoked!");
}
}
@Test
public void invokeTest() {
WithDefaultMethod proxy = (WithDefaultMethod) Proxy.newProxyInstance(
WithDefaultMethod.class.getClassLoader(),
new Class<?>[] { WithDefaultMethod.class }, this);
proxy.someDefaultMethod();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// assuming not knowing the interface before runtime (I wouldn't use a
// proxy, would I?)
// what to do here to get the line printed out?
// This is just a loop
// method.invoke(this, args);
return null;
}
}
Принятый ответ использует setAccessible(true) для перехода к MethodHandles.Lookup, что ограничено в Java 9 и более поздних версиях. В этом почте описывается изменение JDK, которое работает для Ява 9 или более поздняя версия.
Это можно заставить работать на Java 8 (и более поздних версиях), если вы можете заставить автора интерфейса вызывать вашу утилиту с экземпляром MethodHandles.Lookup, созданным в интерфейсе (таким образом, он получает разрешение на доступ к методам по умолчанию для интерфейс):
interface HelloGenerator {
public static HelloGenerator createProxy() {
// create MethodHandles.Lookup here to get access to the default methods
return Utils.createProxy(MethodHandles.lookup(), HelloGenerator.class);
}
abstract String name();
default void sayHello() {
System.out.println("Hello " + name());
}
}
public class Utils {
static <P> P createProxy(MethodHandles.Lookup lookup, Class<P> type) {
InvocationHandler handler = (proxy, method, args) -> {
if (method.isDefault()) {
// can use unreflectSpecial here, but only because MethodHandles.Lookup
// instance was created in the interface and passed through
return lookup
.unreflectSpecial(method, method.getDeclaringClass())
.bindTo(proxy)
.invokeWithArguments(args);
}
return ...; // your desired proxy behaviour
};
Object proxy = Proxy.newProxyInstance(
type.getClassLoader(), new Class<?>[] {type}, handler);
return type.cast(proxy);
}
}
Этот подход не справится со всеми вариантами использования Java 8, но с моим он справился.
Ответ 3 · рейтинг 3 · Eugene
Поскольку jdk-16 это поддерживается собственным способом через invokeDefault.
Он включает в себя код из фреймворка Spring для простой и эффективной обработки различных случаев.
Ответ 5 · Infernoz
Это раздражающе глупое нелогичное поведение, которое, как я утверждаю, является ошибкой в методе #invoke(Object,Object[]), потому что вы не можете сделать вещи простыми в InvocationHandler, например:
if (method.isDefault())
method.invoke(proxy, args);
else
method.invoke(target, args); // to call a wrapped object
Поэтому нужно выполнить специальный поиск MethodHandle и привязать его к прокси-серверу, чтобы вызвать его.
Я усовершенствовал предоставленный McDowell код следующим образом (упрощенно):
фасадИнтерфейс - это проксируемый интерфейс, который объявляет метод по умолчанию, вероятно, также можно будет использовать методы по умолчанию суперинтерфейса.
Неигрушечный код должен выполнять этот поиск перед вызовом вызова или, по крайней мере, кэшировать MethodHandle.