Java8 dynamic proxy and default methods

Since jdk-16 this is supported in a native way, via invokeDefault.

To your example, this would be done as:

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

}

But you do not need an explicit implementation of the interface that you need, this can be done slightly different:

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

You can use the MethodHandles type in your InvocationHandler. This code is copied from 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);
}

The accepted answer uses setAccessible(true) to break into MethodHandles.Lookup, something that is restricted in Java 9 and beyond. This mail describes a JDK change that works for Java 9 or later.

It is possible to get this to work on Java 8 (and later) if you can get the writer of the interface to call your utility with an instance of MethodHandles.Lookup created in the interface (so it gets the permission to access the default methods of the interface):

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

This approach won't handle all Java 8 use cases, but it did handle mine.

Tags:

Java

Java 8