DEV Community

Allen D. Ball
Allen D. Ball

Posted on • Updated on • Originally published at blog.hcf.dev

Adding Support to Java InvocationHandler Implementations for Interface Default Methods

Java 8 introduced default methods to interfaces. Existing InvocationHandler implementations will not invoke default interface methods. This short article documents the necessary changes. Note: It first describes the implementation based on a reading of the documents and then provides a working implementation for Java 8.

Given the InvocationHandler implementation:

    @Override
    public Object invoke(Object proxy,
                         Method method, Object[] argv) throws Throwable {
        Object result = null;
        /*
         * Logic to calculate result.
         */
        return result;
    }
Enter fullscreen mode Exit fullscreen mode

the Java 8 solution appears to be to extend the implementation to invoke any interface default methods through a MethodHandle:

    @Override
    public Object invoke(Object proxy,
                         Method method, Object[] argv) throws Throwable {
        Object result = null;
        Class<?> declaringClass = method.getDeclaringClass();

        if (method.isDefault()) {
            result =
                MethodHandles.lookup()
                .in(declaringClass)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(argv);
        } else {
            /*
             * Logic to calculate result.
             */
        }

        return result;
    }
Enter fullscreen mode Exit fullscreen mode

If the Method is "default" then the target interface method is invoked. Otherwise, the InvocationHandler implementation processes as before. Any interface default method should be invoked by:

  1. Finding the MethodHandles.Lookup through MethodHandles.lookup().in(declaringClass),
  2. Get a MethodHandle bypassing any overriding methods through .unreflectSpecial(method, declaringClass), and,
  3. Invoke the method on the proxy with .bindTo(proxy).invokeWithArguments(argv)

Unfortunately, this does not work if the declaringClass is not "private-accessible" to the caller (which is most of the time) resulting in:

Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface package1.SomeInterface, from package1.SomeInterface/public
    at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850)
    at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572)
    at java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1231)
    at package2.InvocationHandlerImpl.invoke(InvocationHandlerImpl.java:59)
Enter fullscreen mode Exit fullscreen mode

The actual Java 8 solution is:

    @Override
    public Object invoke(Object proxy,
                         Method method, Object[] argv) throws Throwable {
        Object result = null;
        Class<?> declaringClass = method.getDeclaringClass();

        if (method.isDefault()) {
            Constructor<MethodHandles.Lookup> constructor =
                MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);

            constructor.setAccessible(true);

            result =
                constructor.newInstance(declaringClass)
                .in(declaringClass)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(argv);
        } else {
            /*
             * Logic to calculate result.
             */
        }

        return result;
    }
Enter fullscreen mode Exit fullscreen mode

This will not work in Java 9+. In Java 9 and subsequent releases, the solution should be based on MethodHandles.Lookup.findSpecial() and/or MethodHandles.privateLookupIn().

Top comments (0)