Java8 dynamic proxy and default methods

后端 未结 4 1253
北荒
北荒 2021-01-01 20:43

Having a dynamic proxy for an interface with default methods, how do I invoke a default method? By using something like defaultmethod.invoke(this, ...) you just

4条回答
  •  梦谈多话
    2021-01-01 21:04

    This is annoyingly stupid counter-intuitive behaviour, which I assert is a bug in method#invoke(Object,Object[]), because you can't keep things simple in an InvocationHandler, like:

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

    So have to do a special lookup for a MethodHandle, and bind to proxy, to call, it.

    I refined the McDowell provided code as follows (simplified):

    private static final Constructor 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 is the interface being proxied, which declares the default method, it will probably be possible to use super-interface default methods too.

    Non-toy code should do this lookup before invoke is called, or at least cache the MethodHandle.

提交回复
热议问题