写在前面
试想一下,有很多场景下,我们有特殊要求,希望在JDK动态代理的接口中希望有自己自定义的逻辑,而后再执行代理方法。譬如:
interface ProxyInterface { Object send(Object msg); default Object checkThenSend(Object msg) { // 此处可以检查 msg,以及一些默认赋值 msg.setProps(xxx); // 再调用执行方法 return send(msg); } }
抽象方法send()的参数 msg,我们希望设置一些公共的参数,但是在每一处调用时都设置一次,就显得重复啰嗦。因此,在checkThenSend()中就可以统一设置值,然后再调用 send()方法。
本文就是如何实现从而达到目的。
实现原理
jdk8中如果直接调用MethodHandles#lookup()获取到的link MethodHandles.Lookup 在调用方法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)获取父类方法句柄MethodHandle}时, 可能出现权限不够, 抛出如下异常, 所以通过反射创建{@link MethodHandles.Lookup}解决该问题. java.lang.IllegalAccessException: no private access for invokespecial: 而jdk11中直接调用MethodHandles#lookup()获取到的MethodHandles.Lookup, 也只能对接口类型才会权限获取方法的方法句柄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 Logger logger = LoggerFactory.getLogger(MethodHandlesUtil.class);
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方法,
//如果没有说明是jdk8的版本.(不考虑jdk8以下版本)
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);
}
}
}
应用
以下是在JDK动态代理中如何执行代理接口中的default方法。
import java.lang.invoke.MethodHandle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler<T> implements InvocationHandler {
private Class<T> interfaceClass;
private final static Map<Method, MethodHandle> DEFAULT_METHOD_HANDLE = new ConcurrentHashMap<>();
public T bind(Class<T> interfaceClass) {
this.interfaceClass = interfaceClass;
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, this);
}
/**
* 处理代理实例上的方法调用并返回结果。
* 当在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final Class<T> iCls = this.interfaceClass;
// 如果是默认方法,需要获取方法句柄才能执行,原因是无法走代理
if (method.isDefault()) {
return DEFAULT_METHOD_HANDLE.computeIfAbsent(
method, m -> MethodHandlesUtil.getSpecialMethodHandle(method).bindTo(proxy)).invokeWithArguments(args);
}
// 正常执行其他逻辑
return args;
}
}