【问题标题】:Java8 dynamic proxy and default methodsJava8 动态代理和默认方法
【发布时间】:2014-10-05 19:56:22
【问题描述】:

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

【问题讨论】:

    标签: java java-8


    【解决方案1】:

    由于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!");
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      接受的答案使用setAccessible(true) 闯入MethodHandles.Lookup,这在Java 9 及更高版本中受到限制。此mail 描述了适用于 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);
        }
      }
      

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

      【讨论】:

      • 也许您可以从链接到答案的电子邮件中添加解决方案。今天阅读本文的任何人都可能希望使用该解决方案(使用 findSpecial)而不是 Java 8 解决方法。
      • @Jorn 最后在a native way中得到支持
      【解决方案3】:

      您可以在您的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);
      }
      

      【讨论】:

      • 这行得通。我还需要一个提示,我必须在调用处理程序中知道代理对象。非常感谢!
      • @Eugene 错字,我想,“不”应该是“现在”。
      【解决方案4】:

      我写了一篇博文,详细介绍了 Java 8 和 9+ 必须使用的不同方法:http://netomi.github.io/2020/04/17/default-methods.html

      它包含来自 spring 框架的代码,以干净有效的方式处理不同的情况。

      【讨论】:

        【解决方案5】:

        这是非常愚蠢的反直觉行为,我断言这是 method#invoke(Object,Object[]) 中的一个错误,因为您不能在 InvocationHandler 中保持简单,例如:

        if (method.isDefault())
            method.invoke(proxy, args);
        else
            method.invoke(target, args); // to call a wrapped object
        

        因此必须对 MethodHandle 进行特殊查找,并绑定到代理,然后调用它。

        我改进了 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 是被代理的接口,它声明了默认方法,也可能使用超接口默认方法。

        非玩具代码应该在调用调用之前进行此查找,或者至少缓存 MethodHandle。

        【讨论】:

          猜你喜欢
          • 2015-08-10
          • 2023-03-04
          • 2016-10-15
          • 2016-09-06
          • 2017-02-16
          • 2016-04-04
          • 1970-01-01
          • 2014-07-23
          相关资源
          最近更新 更多