从JDK8开始,接口支持默认方法实现,即在接口中可以有具体的实现,仅需使用关键字 default修饰方法即可,如:
public interface MyInterface {
default void call(String methodName) {
System.out.println("MethodHandles, call method: " + methodName);
}
}
那么,如果该接口我们不想有实现类,又想要调用 MyInterface#call 方法怎么办?
此时,可借助JDK给我们提供的方法句柄实现:java.lang.invoke.MethodHandles,从而可动态调用方法。
对于方法句柄的调用实现,不同JDK版本可略有差异,简单说明:
jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link MethodHandles.Lookup}
在调用方法 {@link MethodHandles.Lookup#findSpecial(java.lang.Class, java.lang.String, java.lang.invoke.MethodType, java.lang.Class)}
和{@link MethodHandles.Lookup#unreflectSpecial(java.lang.reflect.Method, java.lang.Class)}
获取父类方法句柄{@link MethodHandle}时
可能出现权限不够, 抛出如下异常, 所以通过反射创建{@link MethodHandles.Lookup}解决该问题.
java.lang.IllegalAccessException: no private access for invokespecial: interface com.example.demo.methodhandle.UserService, from com.example.demo.methodhandle.UserServiceInvoke
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850)
at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572)
而jdk11中直接调用{@link MethodHandles#lookup()}获取到的{@link MethodHandles.Lookup},也只能对接口类型才会权限获取方法的方法句柄{@link MethodHandle}.
如果是普通类型Class,需要使用jdk9开始提供的 MethodHandles#privateLookupIn(java.lang.Class, java.lang.invoke.MethodHandles.Lookup)方法.
注意:接口没有实现,不能直接通过反射调用!!
以下是针对不同版本编写的一个工具类
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public final class MethodHandlesUtil {
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE
| MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE
| MethodHandles.Lookup.PUBLIC;
private static Constructor<MethodHandles.Lookup> java8LookupConstructor;
private static Method privateLookupInMethod;
static {
//jdk9开始提供的java.lang.invoke.MethodHandles.privateLookupIn方法
try {
privateLookupInMethod = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupInMethod = null;
logger.info("There is no [java.lang.invoke.MethodHandles.privateLookupIn(Class, Lookup)] method in this version of JDK");
}
//jdk8其实也适用于jdk9及以上的版本,但是上面优先,可以避免 jdk9 反射警告
if (privateLookupInMethod == null) {
try {
java8LookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
java8LookupConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
//可能是jdk8 以下版本
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
}
}
}
/**
* java9中的MethodHandles.lookup()方法返回的Lookup对象
* 有权限访问specialCaller != lookupClass()的类
* 但是只能适用于接口, {@link java.lang.invoke.MethodHandles.Lookup#checkSpecialCaller}
*/
public static MethodHandles.Lookup lookup(Class<?> callerClass) {
//使用反射,因为当前jdk可能不是java9或以上版本
if (privateLookupInMethod != null) {
try {
return (MethodHandles.Lookup) privateLookupInMethod.invoke(MethodHandles.class, callerClass, MethodHandles.lookup());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
//jdk8
try {
return java8LookupConstructor.newInstance(callerClass, ALLOWED_MODES);
} catch (Exception e) {
throw new IllegalStateException("no 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", e);
}
}
public static MethodHandle getSpecialMethodHandle(Method parentMethod) {
final Class<?> declaringClass = parentMethod.getDeclaringClass();
MethodHandles.Lookup lookup = lookup(declaringClass);
try {
return lookup.unreflectSpecial(parentMethod, declaringClass);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
简单使用,执行 MyInterface#call方法调用
利用JDK动态代理创建一个代理类
public static void main(String[] args) {
// 创建JDK代理类
MyInterface myInterface = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[]{MyInterface.class},
(proxy, method, arg) -> MethodHandlesUtil.getSpecialMethodHandle(method)
.bindTo(proxy)
.invokeWithArguments(arg));
// 调用call方法
myInterface.call("MyInterface#call");
}
执行结果
可能有人就要问了,为什么不能直接调用 method.invoke(proxy, arg)?
原因很简单,JDK动态代理接口的对象本身就是java.lang.reflect.InvocationHandler,如果再调用method.invoke(proxy, arg),那么就会陷入无限循环中。因此,只能采取其他方式,比如以上获取的是接口的默认实现方法;当然,如果是接口的抽象方法,那么就可以在代理方法java.lang.reflect.InvocationHandler#invoke实现自己的逻辑,比如调用外部接口的处理逻辑等等。
最后,感谢大佬,借鉴:java8中实现调用对象的父类方法_privatelookupin-优快云博客