Java8动态代理和默认方法

Cfx*_*Cfx 14 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

McD*_*ell 15

您可以在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);
}
Run Code Online (Sandbox Code Playgroud)


Jod*_*hen 7

接受的答案用于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> 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

这种方法不能处理所有 Java 8 用例,但它确实处理了我的用例。

  • 也许您可以从链接到答案的电子邮件中添加解决方案。今天阅读本文的任何人可能都希望使用该解决方案(使用“findSpecial”)而不是 Java 8 解决方法。 (2认同)

Eug*_*ene 7

从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;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

但是您不需要显式实现所需的接口,这可以稍微不同:

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!");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)