Java8动态代理和默认方法

拥有使用默认方法的接口的动态代理,如何调用默认方法? 通过使用defaultmethod.invoke(this, ...)类的东西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; } } 

您可以在InvocationHandler中使用MethodHandles类型。 此代码是从Zero Turnaround复制的。

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

接受的答案使用setAccessible(true)来分解MethodHandles.Lookup ,这在Java 9及更高版本中受到限制。 此邮件描述了适用于Java 9或更高版本的JDK更改。

如果您可以让接口的编写者使用在接口中创建的MethodHandles.Lookup实例来调用您的实用程序,那么可以在Java 8(及更高版本)上使用它(因此它获得访问默认方法的权限)的界面):

 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 createProxy(MethodHandles.Lookup lookup, Class

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用例,但它确实处理了我的用例。

Proxy实现其所有支持的接口的方法。 它们都调用创建ProxyInvocationHandler

Proxy用于将调用委托给实际实例。 default方法已被代理覆盖,因此无法直接调用。 Proxy将拦截所有调用并将它们传递给InvocationHandler

在代理中包装接口的实际实例并委托给它。

在尝试理解在该接口的代理上reflection性地调用默认接口方法的问题时,我发现这篇文章非常有用。