Java 8 default interface methods and JDK dynamic proxies


Java 8 added the ability to support default methods in interfaces.
That’s great for a lot of reasons and it works fine but what about
dynamic proxies? Are the default method skipped when needed? Are they
forwarded to the invocation handler?

Well JVM is well done and it is forwarded to the InvocationHandler. It
means your invocation handler invoke(proxy, method, args) method is
invoked when calling an interface method with a default….which
sounds logical since after all that’s an interface method.

When you use proxies to fully decorate an existing instance no issue,
if you call the default method your proxy will call the same method on
the delegate instance and if it should use the default it will….but
if you hack the proxy to implement it on the
fly without any delegate how to do?

In this case you know that if a method is a default one you need to
call the default implementation of the interface. Here are two issues:

  1. how to know a method is a default one?
  2. How to call the default implementation?

First issue is easy to solve, Java 8 added to its reflection API
java.lang.reflect.Method.isDefault(). Side note is you can check it in
previous java versions too backporting its implementation to an
utility method:

public boolean
isDefault() {
  return ((getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) ==
          Modifier.PUBLIC) &&
getDeclaringClass().isInterface();
}

The second issue is a bit more complex. If you call
method.invoke(proxy, args) you’ll loop in the proxy since the proxy
will try to invoke the real method which is the proxy one actually.

To workaround it you need to use java 7 new invoke API (MethodHandle).
The methods to have a look are the *Special ones which basically skip
the overriding checks.

Here how to do:

MethodHandles.lookup()
  .in(declaringClass)
  .unreflectSpecial(method, declaringClass)
  .bindTo(proxy)
  .invokeWithArguments(args);

First step is to get a lookup (kind of method handle factory), then
force the declaring class (default is caller class), unwrap the
java.lang.reflect.Method to a “special” MethodHandler and finally
invoke it on the proxy. Since it is “special” it will skip the
overriding done by the proxying and invoke the default implementation
:).

Here what the InvocationHandler finally looks like:

final
InvocationHandler handler = (proxy, method, args) -> {
if (method.isDefault())
{
final Class<?> declaringClass = method.getDeclaringClass();
return
MethodHandles.lookup()
.in(declaringClass)
.unreflectSpecial(method, declaringClass)
.bindTo(proxy)
.invokeWithArguments(args);
}

// proxy impl of not defaults methods
return null;
};

This opens several interesting doors. For instance with EJBs you could – if the spec goes this way – decorate an interface default method with @Asynchronous and return an AsyncResult just wrapping the other method. This way you get for all implementations asychronous calls for free from the implementations point of view.

Where it can go weird is if you can inject in parameters CDI beans or any injectable EE resources…then you don’t need classes anymore 😉

Want more content ? Keep up-to-date on my new blog

Or stay in touch on twitter @rmannibucau

Advertisement

6 thoughts on “Java 8 default interface methods and JDK dynamic proxies

  1. Marco

    Unfortunately that doesn’t work for me.
    unreflectSpecial throws an exception:
    java.lang.IllegalAccessException: no private access for invokespecial

    As described in the docs:
    http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.Lookup.html#unreflectSpecial-java.lang.reflect.Method-java.lang.Class-
    the lookup object must have private access which is not the case if it has not bee created by the interface class itself.

    Did you do anything else?

    Reply
    1. rmannibucau Post author

      You have several solution to force private access:

      final InvocationHandler handler = (proxy, method, args) -> {
      	if (method.isDefault())
      	{
      		final Class<?> declaringClass = method.getDeclaringClass();
      		final MethodHandles.Lookup lookup = MethodHandles.publicLookup()
      				.in(declaringClass);
      
      		// ensure allowed mode will not check visibility
      		final Field f = MethodHandles.Lookup.class.getDeclaredField("allowedModes");
      		final int modifiers = f.getModifiers();
      		if (Modifier.isFinal(modifiers)) { // should be done a single time
      			final Field modifiersField = Field.class.getDeclaredField("modifiers");
      			modifiersField.setAccessible(true);
      			modifiersField.setInt(f, modifiers & ~Modifier.FINAL);
      			f.setAccessible(true);
      			f.set(lookup, MethodHandles.Lookup.PRIVATE);
      		}
      
      		return lookup
      				.unreflectSpecial(method, declaringClass)
      				.bindTo(proxy)
      				.invokeWithArguments(args);
      	}
      
      	// proxy impl of not defaults methods
      	return null;
      };
      

      or

      final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
      if (!constructor.isAccessible()) {
      	constructor.setAccessible(true);
      }
      final InvocationHandler handler = (proxy, method, args) -> {
      	if (method.isDefault())
      	{
      		final Class<?> declaringClass = method.getDeclaringClass();
      		return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
      				.unreflectSpecial(method, declaringClass)
      				.bindTo(proxy)
      				.invokeWithArguments(args);
      	}
      
      	// proxy impl of not defaults methods
      	return null;
      };
      
      Reply
      1. Marco

        That worked. I used the second option since that one looks a litte less hacky. 😉
        In my opinion java should provide an ‘official’ way to do that. For example:
        Method.invokeDefault(Class c, Object obj, Object… args)

        Anyway. Thank you very much for your article + reply! I couldn’t find anything on the topic elsewhere.

  2. welyabpaula

    Hey.
    Is the message “no private access for invokespecial” some kind of BUG?
    I really don’t found any official documentation about call about call default methods from Java proxy API.

    When the interface is a inner type of class where I put main method to test, your first solution works, but if a place the interface in a separated file, the exception is thrown.

    For me, use the reflection to change Java API is a design problem. May be default method methods were protected to be no called from proxies.

    Reply
    1. rmannibucau Post author

      I dont think it is a bug.

      About the design: yes and no. Such solutions are most of the time used in frameworks and in such a case it can be justified.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s