简体   繁体   中英

How do I invoke Java 8 default methods reflectively

Given this simple "Hello World"ish Java 8 interface, how do I invoke its hello() method via reflection?

public interface Hello {
    default String hello() {
        return "Hello";
    }
}

You could use MethodHandles for that:

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ReflectiveDefaultMethodCallExample {

    static interface Hello {
        default String hello() {
            return "Hello";
        }
    }

    public static void main(String[] args) throws Throwable{

        Hello target =
                //new Hello(){};
                (Hello)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{Hello.class}, (Object proxy, Method method, Object[] arguments) -> null);
        Method method = Hello.class.getMethod("hello");

        Object result = MethodHandles.lookup()
                                     .in(method.getDeclaringClass())
                                     .unreflectSpecial(method,method.getDeclaringClass())
                                     .bindTo(target)
                                     .invokeWithArguments();
        System.out.println(result); //Hello
    }
}

Unfortunately, there doesn't seem to be an ideal solution that works on all of JDK 8, 9, 10, which behave differently. I've run into issues when fixing an issue in jOOR . I've also blogged about the correct solution here in detail .

This approach works in Java 8

In Java 8, the ideal approach uses a hack that accesses a package-private constructor from Lookup :

import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;

interface Duck {
    default void quack() {
        System.out.println("Quack");
    }
}

public class ProxyDemo {
    public static void main(String[] a) {
        Duck duck = (Duck) Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] { Duck.class },
            (proxy, method, args) -> {
                Constructor<Lookup> constructor = Lookup.class
                    .getDeclaredConstructor(Class.class);
                constructor.setAccessible(true);
                constructor.newInstance(Duck.class)
                    .in(Duck.class)
                    .unreflectSpecial(method, Duck.class)
                    .bindTo(proxy)
                    .invokeWithArguments();
                return null;
            }
        );

        duck.quack();
    }
}

This is the only approach that works with both private-accessible and private-inaccessible interfaces. However, the above approach does illegal reflective access to JDK internals, which will no longer work in a future JDK version, or if --illegal-access=deny is specified on the JVM.

This approach works on Java 9 and 10, but not 8

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Proxy;

interface Duck {
    default void quack() {
        System.out.println("Quack");
    }
}

public class ProxyDemo {
    public static void main(String[] a) {
        Duck duck = (Duck) Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] { Duck.class },
            (proxy, method, args) -> {
                MethodHandles.lookup()
                    .findSpecial( 
                         Duck.class, 
                         "quack",  
                         MethodType.methodType(void.class, new Class[0]),  
                         Duck.class)
                    .bindTo(proxy)
                    .invokeWithArguments();
                return null;
            }
        );

        duck.quack();
    }
}

Solution

Simply implement both of the above solutions and check if your code is running on JDK 8 or on a later JDK and you'll be fine. Until you're not :)

You can't call it directly, as you need an instance of an implementing class. And for that, you need an implementing class. default method is not a static method, and neither you can create an instance of an interface.

So, suppose you've an implementing class:

class HelloImpl implements Hello {  }

You can invoke the method like this:

Class<HelloImpl> clazz = HelloImpl.class;
Method method = clazz.getMethod("hello");
System.out.println(method.invoke(new HelloImpl()));  // Prints "Hello"

I've found a solution to creating instances from interfaces like the above reflectively using code from sun.misc.ProxyGenerator which defines a class HelloImpl by assembling bytecode. Now I'm able to write:

Class<?> clazz = Class.forName("Hello");
Object instance;

if (clazz.isInterface()) {
    instance = new InterfaceInstance(clazz).defineClass().newInstance();
} else {
    instance = clazz.newInstance();
}

return clazz.getMethod("hello").invoke(instance);

...but that's pretty ugly.

Huge thanks to Lukas. Here is his answer with the Java 8 vs 9+ check and support for non-void returns and arguments. Be sure to give his answer an upvote.

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;

public class ThanksLukas implements InvocationHandler {
    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {

        if (method.isDefault()) {
            final float version = Float.parseFloat(System.getProperty("java.class.version"));
            if (version <= 52) {
                final Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class);
                constructor.setAccessible(true);

                final Class<?> clazz = method.getDeclaringClass();
                return constructor.newInstance(clazz)
                        .in(clazz)
                        .unreflectSpecial(method, clazz)
                        .bindTo(proxy)
                        .invokeWithArguments(args);
            } else {
                return MethodHandles.lookup()
                        .findSpecial(
                                method.getDeclaringClass(),
                                method.getName(),
                                MethodType.methodType(method.getReturnType(), new Class[0]),
                                method.getDeclaringClass()
                        ).bindTo(proxy)
                        .invokeWithArguments(args);
            }
        }

        // your regular proxy fun here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM