Skip to content

rntrp/default-method-lookup-utils

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

default-method-lookup-utils

A small utility class for invoking default interface methods.

Imagine, you have an interface doing something like this:

interface Dog {
    default void woof() {
        System.out.println("woof");
    }
}

The utility class allows you to

  1. invoke the overridden default method on an instance (yes, it's possible)
  2. delegate the invocation call to the default method when using java.lang.reflect.Proxy

Invoking Overridden Default Methods

First, we implement the interface, e.g. by instantiating an anonymous class:

Dog dog = new Dog() {
    @Override
    public void woof() {
        System.out.println("meow");
    }
};

By now our dog will only meow when we command it to woof:

dog.woof(); // Obviously, meow

Method woof = Dog.class.getMethod("woof");
woof.invoke(dog); // Also meow

Can we make our dog woof again? Now that's where our utility class comes in:

DefaultMethodLookupUtils.invokeDefaultMethod(dog, woof); // woof!

This case somewhat defies the common understanding of Java polymorphism, as invoking superclass methods from an instance is not allowed under normal circumstances. Java 7 introduced the java.lang.invoke.MethodHandle infrastructure, which allows for more intricate reflective access. DefaultMethodLookupUtils makes use of this API to overcome the limitations regarding the superclass method invocation on objects.

Default Methods & java.lang.reflect.Proxy

IMHO, that's the main use case for the utility class. The idea behind java.lang.reflect.Proxy is to act as an extra layer of program logic when calling object methods. It may forward method calls to some kind of delegate object, or it may execute its own logic behind curtains. This is, for instance, an important part of the famous "Spring magic". Also, most Java mock frameworks are basically designed around Java proxies.

Sometimes, when you create a proxy object for an interface, you might also want to invoke default methods as is, and do some generic stuff for all other methods. Example:

Dog dog = (Dog) Proxy.newProxyInstance(
        Thread.currentThread().getContextClassLoader(),
        new Class[]{Dog.class},
        (proxy, method, args) -> method.isDefault()
            ? method.invoke(proxy) // call the default method
            : null); // for other methods simply return null

dog.woof(); // will it woof?

What we expect here, is that method.invoke(proxy) will somehow delegate our method call to the default method. Unfortunately, it doesn't happen. Our dog doesn't woof. Instead, it throws a lengthy stacktrace of pretty vague InvocationTargetExceptions at us.

Let's try it again, but this time with the DefaultMethodLookupUtils:

Dog dog = (Dog) Proxy.newProxyInstance(
        Thread.currentThread().getContextClassLoader(),
        new Class[]{Dog.class},
        (proxy, method, args) -> method.isDefault()
            ? DefaultMethodLookupUtils.invokeDefaultMethod(proxy, method, args)
            : null);

dog.woof(); // woof, at last

This time around our default method will be called, and we can finally lean back.

Under the Hood

Implementation is based on the great DZone article "Correct Reflective Access to Interface Default Methods in Java 8, 9, 10". Kudos to Lukas Eder for pointing out the differences between Java versions and providing a working solution for newer JREs.

Even though the MethodHandle API is present since Java 7, its behaviour depends on the runtime version:

  • Java 7 and 8 must call a private constructor of MethodHandles.Lookup. Obviously, it's no good, if the program runs under a SecurityManager with draconian security policy, so you may want to loosen these restrictions. Since default methods were introduced in Java 8, on Java 7 the utility class functionality is limited to calling overridden object methods. This approach throws an IllegalAccessException with later Java versions.
  • With Java 9 onwards there is no need in calling any private constructor, since MethodHandles.lookup() is now capable of finding default interface methods. However, this method doesn't work with Java 7 and 8, but the method itself is present.

DefaultMethodLookupUtils implements both approaches. It checks Java version at runtime and chooses the right one automatically. Code can be compiled once with JDK 7 and will work on all JREs starting with 7.

About

Enables calling default interface methods via reflection

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages