拥有使用默认方法的接口的动态代理,如何调用默认方法?通过使用像defaultmethod.invoke(this, ...)
这样的东西你只需要调用你的代理调用处理程序(这在某种程度上是正确的,因为你没有这个接口的实现类)。
我有一个解决方法,使用ASM创建一个实现接口的类,并将此类调用委托给此类的实例。但这不是一个好的解决方案,特别是如果默认方法调用其他接口方法(你得到一个委托人ping-pong)。对于这个问题,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;
}
}
答案 0 :(得分:14)
您可以在 InvocationHandler 中使用MethodHandles类型。此代码是从Zero Turnaround复制的。
Constructor<MethodHandles.Lookup> constructor;
Class<?> declaringClass;
Object result;
if (method.isDefault()) {
declaringClass = method.getDeclaringClass();
constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);
result = constructor.
newInstance(declaringClass, MethodHandles.Lookup.PRIVATE).
unreflectSpecial(method, declaringClass).
bindTo(proxy).
invokeWithArguments(args);
return(result);
}
答案 1 :(得分:4)
接受的答案使用import { Directive , ElementRef , OnInit , Renderer } from '@angular/core';
@Directive({
selector: '[iframeAutoHeight]'
})
export class IframeAutoHeightDirective implements OnInit {
private el: any;
private renderer: Renderer;
private prevHeight: number;
private sameCount: number;
constructor(_elementRef: ElementRef, _renderer: Renderer) {
this.el = _elementRef.nativeElement;
this.renderer = _renderer;
}
ngOnInit() {
const self = this;
if (this.el.tagName === 'IFRAME') {
this.renderer.listen(this.el, 'load', () => {
self.prevHeight = 0;
self.sameCount = 0;
setTimeout(() => {
self.setHeight();
}, 50);
});
}
}
setHeight() {
const self = this;
if (this.el.contentWindow.document.body.scrollHeight !== this.prevHeight) {
this.sameCount = 0;
this.prevHeight = this.el.contentWindow.document.body.scrollHeight;
this.renderer.setElementStyle(
self.el,
'height',
this.el.contentWindow.document.body.scrollHeight + 'px'
);
setTimeout(() => {
self.setHeight();
}, 50);
} else {
this.sameCount++;
if (this.sameCount < 2) {
setTimeout(() => {
self.setHeight();
}, 50);
}
}
}
}
进入setAccessible(true)
,这在Java 9及更高版本中受到限制。此mail描述了适用于Java 9或更高版本的JDK更改。
如果您可以让接口的编写者使用在接口中创建的MethodHandles.Lookup
实例来调用您的实用程序,那么可以在Java 8(及更高版本)上使用它(因此它获得了权限)访问接口的默认方法):
MethodHandles.Lookup
这种方法不会处理所有Java 8用例,但确实处理了我的用例。
答案 2 :(得分:3)
由于 jdk-16 以原生方式支持,通过 invokeDefault
。
以您的示例为例,这将按以下方式完成:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class InvocationHandlerTest {
public static void main(String[] args) {
WithDefaultMethod proxy = (WithDefaultMethod) Proxy.newProxyInstance(
WithDefaultMethod.class.getClassLoader(),
new Class<?>[] { WithDefaultMethod.class }, new Java8Proxy());
proxy.someDefaultMethod();
}
interface WithDefaultMethod {
void someMethod();
default void someDefaultMethod() {
System.out.println("default method invoked!");
}
}
static class Java8Proxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoked");
InvocationHandler.invokeDefault(proxy, method, args);
return null;
}
}
}
但是您不需要显式实现您需要的接口,这可以稍微不同:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class InvocationHandlerTest {
public static void main(String[] args) {
WithDefaultMethod proxy = (WithDefaultMethod) Proxy.newProxyInstance(
WithDefaultMethod.class.getClassLoader(),
new Class<?>[] { WithDefaultMethod.class },
(o, m, params) -> {
if (m.isDefault()) {
// if it's a default method, invoke it
return InvocationHandler.invokeDefault(o, m, params);
}
return null;
});
proxy.someDefaultMethod();
}
interface WithDefaultMethod {
void someMethod();
default void someDefaultMethod() {
System.out.println("default method invoked!");
}
}
}
答案 3 :(得分:1)
Proxy
实现其支持的所有接口的方法。他们都调用InvocationHandler
创建的Proxy
。
Proxy
用于将调用委托给实际实例。 default
方法已被代理覆盖,因此无法直接调用。 Proxy
将拦截所有呼叫并将其传递给InvocationHandler
。
在代理中包装接口的实际实例并委托给它。
答案 4 :(得分:0)
在尝试理解在该接口的代理上反射性地调用默认接口方法的问题时,我发现this article非常有用。
答案 5 :(得分:0)
我改进了McDowell提供的代码,如下所示(简化):
private static final Constructor<MethodHandles.Lookup> lookupConstructor;
static {
try {
lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookupConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private static MethodHandle findDefaultMethodHandle(Class<?> facadeInterface, Method m) {
try {
Class<?> declaringClass = m.getDeclaringClass();
// Used mode -1 = TRUST, because Modifier.PRIVATE failed for me in Java 8.
MethodHandles.Lookup lookup = lookupConstructor.newInstance(declaringClass, -1);
try {
return lookup.findSpecial(facadeInterface, m.getName(), MethodType.methodType(m.getReturnType(), m.getParameterTypes()), declaringClass);
} catch (IllegalAccessException e) {
try {
return lookup.unreflectSpecial(m, declaringClass);
} catch (IllegalAccessException x) {
x.addSuppressed(e);
throw x;
}
}
} catch (RuntimeException e) {
throw (RuntimeException) e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static class InvocationHandlerImpl implements InvocationHandler {
private final Class<?> facadeInterface;
private Object invokeDefault(Object proxy, Method method, Object[] args) throws Throwable {
MethodHandle mh = findDefaultMethodHandle(facadeInterface, m);
return mh.bindTo(proxy).invokeWithArguments(args);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isDefault()) {
return invokeDefault(proxy, method, args);
}
// rest of code method calls
}
}
facadeInterface是要代理的接口,它声明默认方法,也有可能也可以使用超级接口默认方法。
答案 6 :(得分:0)
我写了一篇博客文章,详细介绍了Java 8和9+必须使用的不同方法:http://netomi.github.io/2020/04/17/default-methods.html
它包含来自spring框架的代码,以干净有效的方式处理各种情况。