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