Java8动态代理和默认方法

时间:2014-10-05 19:56:22

标签: java java-8

拥有使用默认方法的接口的动态代理,如何调用默认方法?通过使用像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;
    }
}

7 个答案:

答案 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框架的代码,以干净有效的方式处理各种情况。