假设有这样一个简单的Java 8接口,类似于"Hello World",如何通过反射调用它的hello()方法?
public interface Hello {
default String hello() {
return "Hello";
}
}
假设有这样一个简单的Java 8接口,类似于"Hello World",如何通过反射调用它的hello()方法?
public interface Hello {
default String hello() {
return "Hello";
}
}
很遗憾,在所有JDK8、9、10上运行的理想解决方案似乎并不存在,它们的行为不同。 在修复jOOR中的问题时,我遇到了一些问题。我在这里也详细介绍了正确的解决方案。
在Java 8中,理想的方法使用从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();
}
}
这是唯一适用于私有可访问和私有不可访问接口的方法。然而,上述方法会非法反射访问JDK内部,这在未来的JDK版本中将不再起作用,或者如果在JVM上指定了--illegal-access=deny
。
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();
}
}
只需实现上述两种解决方案,检查您的代码是否在JDK 8或更高版本上运行,就可以了。直到你不行为止 :)
Duck.class
)作为参数传递。该类可能是一个具体类,它从实现的接口或超类继承了默认方法,或者从继承了默认方法的超接口或从继承了方法的超类继承了默认方法。您必须遍历所有超类,收集唯一的已实现接口,然后找到超级接口的传递闭包,然后尝试每个接口。 - Luke Hutchison您可以使用MethodHandles达到这个目的:
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
}
}
InvocationHandler
如何委托给默认方法。在其他场景中,这可能非常有用。 - Holger非常感谢Lukas。这里是他的答案,包括Java 8与9+的检查和对非void返回和参数的支持。请务必为他的答案点个赞。
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
default
方法不是static
方法,您也无法创建接口的实例。class HelloImpl implements Hello { }
您可以这样调用该方法:
Class<HelloImpl> clazz = HelloImpl.class;
Method method = clazz.getMethod("hello");
System.out.println(method.invoke(new HelloImpl())); // Prints "Hello"
我已经使用sun.misc.ProxyGenerator
中的代码通过反射地创建类似上面的接口实例的方式找到了解决方案。现在,我可以这样编写:
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);
...但那相当丑陋。