1. JDK动态代理
相信大家对JDK的动态代理非常熟悉了,这里简单举个例子说明一下
首先是定义一个接口,然后定义一个类并实现这个接口
public interface ICallback{
void callback();
}
public static class MyCallback implements ICallback {
@Override
public void callback() {
System.out.println("MyCallback");
}
}
接着用 JDK 的 Proxy 生成代理类,在每个方法调用前后都加上一句输出
private static void proxyByJDK(){
ICallback callback = new MyCallback();
Object proxy = java.lang.reflect.Proxy.newProxyInstance(callback.getClass().getClassLoader(), callback.getClass().getInterfaces(), new java.lang.reflect.InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before:" + method.getName());
Object result = method.invoke(callback, args);
System.out.println("after:" + method.getName());
return result;
}
});
((ICallback)proxy).callback();
}
上面的代码很简单,但是他的局限性很大:只能代理接口,而不能代理实体类
那么能不能自己实现一种动态代理来代理实体类呢,经过我的实践,发现是可以的。需要用到 Javassist 这个工具
2. 实体类动态代理的分析
这篇文章不是专门讲解 Javassist,有兴趣的可以去了解一下。Javassist官方文档传送门,感觉英文吃力的可以看国内大牛翻译的 Javassist文档翻译
这里我们只需要知道添加 javassist 的依赖
implementation 'org.javassist:javassist:3.20.0-GA'
在实现实体类的动态代理前,我们先要分析接口和实体类的区分
- 接口的方法默认都是 public 的,所有实现接口的类默认都会全部继承所有接口;而实体类的方法有 private、protected、public 和 default 的区别
- 实现接口可以直接使用默认无参构造函数;而继承实体类有多个构造函数需要继承,并且需要制定一个构造函数来实例化代理对象
- 接口的方法都不是 final 的;而实体类的方法可能是 final 的
- 接口的方法都不是 static 的;而实体类的方法可能是 static 的
再梳理一下,模仿JDK的动态代理的设计思路,实现动态代理实体类所需要的步骤
- 定义 InvocationHandler 类,用于代理对象的方法调用时的回调
- 根据 classloader 和 class 判断是否有缓存,如果有则直接从缓存获取。否则再次生成class并在同一个 classloader 加载的话会出现问题
- 判断被代理对象是否是final的,不是final才进行下一步
- 用javassist新建一个类,包名和被代理类一致,采用Proxy_前缀命名
- 设置代理类的修饰符和继承关系
- 添加成员变量 InvocationHandler,便于后面方法调用时的回调
- 添加构造器,规则是在被代理类的所有构造器的基础上,添加 InvocationHandler 作为第一个参数
- 添加方法,规则是在被代理类的所有方法上,筛选 public、projected、default 的方法,方法体直接调用 InvocationHandler 的 invoke 方法
- 用 classloader 生成 class,并放入缓存
- 根据被代理对象的构造函数,在参数前面加一个 InvocationHandler 参数来实例化代理对象
3. 实现实体类动态代理
首先模仿 JDK 实现一个代理方法的回调接口
public interface InvocationHandler {
/**
* @param proxy 动态生成的代理对象
* @param method 调用的方法
* @param args 调用的参数
* @return 该方法的返回值
* @throws Throwable
*/
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
然后就是代理器的实现
public class Proxy {
// 动态生成代理类的前缀
private static final String PROXY_CLASSNAME_PREFIX = "$Proxy_";
// 缓存容器,防止生成同一个Class文件在同一个ClassLoader加载崩溃的问题
private static final Map<String, Class<?>> proxyClassCache = new HashMap<>();
/**
* 缓存已经生成的代理类的Class,key值根据 classLoader 和 targetClass 共同决定
*/
private static void saveProxyClassCache(ClassLoader classLoader, Class<?> targetClass, Class<?> proxyClass) {
String key = classLoader.toString() + "_" + targetClass.getName();
proxyClassCache.put(key, proxyClass);
}
/**
* 从缓存中取得代理类的Class,如果没有则返回 null
*/
private static Class<?> getProxyClassCache(ClassLoader classLoader, Class<?> targetClass) {
String key = classLoader.toString() + "_" + targetClass.getName();
return proxyClassCache.get(key);
}
/**
* 返回一个动态创建的代理类,此类继承自 targetClass
*
* @param classLoader 从哪一个ClassLoader加载Class
* @param invocationHandler 代理类中每一个方法调用时的回调接口
* @param targetClass 被代理对象
* @param targetConstructor 被代理对象的某一个构造器,用于决定代理对象实例化时采用哪一个构造器
* @param targetParam 被代理对象的某一个构造器的参数,用于实例化构造器
* @return
*/
public static Object newProxyInstance(ClassLoader classLoader,
InvocationHandler invocationHandler,
Class<?> targetClass,
Constructor<?> targetConstructor,
Object... targetParam) {
if (classLoader == null || targetClass == null || invocationHandler == null) {
throw new IllegalArgumentException("argument is null");
}
try {
// 查看是否有缓存
Class<?> proxyClass = getProxyClassCache(classLoader, targetClass);
if (proxyClass != null) {
// 实例化代理对象
return newInstance(proxyClass, invocationHandler, targetConstructor, targetParam);
}
ClassPool pool = new ClassPool(true);
pool.importPackage(InvocationHandler.class.getName());
pool.importPackage(Method.class.getName());
// 被代理类
CtClass targetCtClass = pool.get(targetClass.getName());
// 检查被代理类是否是 final的
if ((targetCtClass.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
throw new IllegalArgumentException("class is final");
}
// 新建代理类
CtClass proxyCtClass = pool.makeClass(generateProxyClassName(targetClass));
// 设置描述符
proxyCtClass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
// 设置继承关系
proxyCtClass.setSuperclass(targetCtClass);
// 添加构造器
addConstructor(pool, proxyCtClass, targetCtClass);
// 添加方法
addMethod(proxyCtClass, targetCtClass, targetClass);
// 从指定ClassLoader加载Class
proxyClass = proxyCtClass.toClass(classLoader, null);
// 缓存
saveProxyClassCache(classLoader, targetClass, proxyClass);
// 输出到文件保存,用于debug调试
// File outputFile = new File("/Users/jm/Downloads/Demo");
// proxyCtClass.writeFile(outputFile.getAbsolutePath());
proxyCtClass.defrost();
// 实例化代理对象
return newInstance(proxyClass, invocationHandler, targetConstructor, targetParam);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 生成代理类的类名生成规则
*/
private static String generateProxyClassName(Class<?> targetClass) {
return targetClass.getPackage().getName() + "." + PROXY_CLASSNAME_PREFIX + targetClass.getSimpleName();
}
/**
* 根据被代理类的构造器,构造代理类对象。代理类的所有构造器都是被代理类构造器前添加一个invocationHandler 参数
*
* @param proxyClass 代理类
* @param invocationHandler 代理类所有构造器的第一个参数
* @param targetConstructor 被代理类的构造器
* @param targetParam 被代理类的构造器的参数
* @return
* @throws Exception
*/
private static Object newInstance(Class<?> proxyClass,
InvocationHandler invocationHandler,
Constructor<?> targetConstructor,
Object... targetParam) throws Exception {
Class[] parameterTypes = new Class[targetConstructor.getParameterTypes().length + 1];
merge(parameterTypes, InvocationHandler.class, targetConstructor.getParameterTypes());
Constructor<?> constructor = proxyClass.getConstructor(parameterTypes);
Object[] paramter = new Object[targetParam.length + 1];
merge(paramter, invocationHandler, targetParam);
return constructor.newInstance(paramter);
}
/**
* 代理类添加构造器,基于被代理类的构造器,在所有参数开头添加一个 {@link InvocationHandler} 参数
*
* @param pool
* @param proxyClass
* @param targetClass
* @throws Exception
*/
private static void addConstructor(ClassPool pool, CtClass proxyClass, CtClass targetClass) throws Exception {
// 添加 invocationHandler 字段
CtField field = CtField.make("private InvocationHandler invocationHandler = null;", proxyClass);
proxyClass.addField(field);
CtConstructor[] constructors = targetClass.getConstructors();
for (CtConstructor constructor : constructors) {
CtClass[] parameterTypes = new CtClass[constructor.getParameterTypes().length + 1];
merge(parameterTypes, pool.get(InvocationHandler.class.getName()), constructor.getParameterTypes());
CtConstructor newConstructor = new CtConstructor(parameterTypes, proxyClass);
// 因为第一个参数指定为 InvocationHandler,所以super()的参数是从2开始的
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= constructor.getParameterTypes().length; i++) {
sb.append("$").append(i + 1).append(",");
}
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
}
// 添加构造器方法
String code = String.format("{super(%s);this.invocationHandler = $1;}", sb.toString());
newConstructor.setBody(code);
proxyClass.addConstructor(newConstructor);
}
}
/**
* 先添加 Public 的方法,然后添加 Project 和 default 的方法
*
* @param proxyCtClass 代理类
* @param targetCtClass 被代理类
* @param targetClass 被代理类
* @throws Exception
*/
private static void addMethod(CtClass proxyCtClass, CtClass targetCtClass, Class<?> targetClass) throws Exception {
int methodNameIndex = 0;
methodNameIndex = addMethod(proxyCtClass, targetCtClass, targetClass.getMethods(), targetCtClass.getMethods(), true, methodNameIndex);
addMethod(proxyCtClass, targetCtClass, targetClass.getDeclaredMethods(), targetCtClass.getDeclaredMethods(), false, methodNameIndex);
}
/**
* 代理类添加方法,基于被代理类的共有方法。因为{@link CtClass#getMethods()} 和 {@link Class#getMethods()}返回的列表顺序不一样,所以需要做一次匹配
*
* @param proxyCtClass 代理类
* @param targetCtClass 被代理类
* @param methods 被代理类的方法数组
* @param ctMethods 被代理类的方法数组
* @param isPublic 是否是共有方法,是:只包含public方法;否:包含projected和default方法
* @param methodNameIndex 新建方法的命名下标
* @return
* @throws Exception
*/
private static int addMethod(CtClass proxyCtClass, CtClass targetCtClass, Method[] methods, CtMethod[] ctMethods, boolean isPublic, int methodNameIndex) throws Exception {
for (int i = 0; i < ctMethods.length; i++) {
CtMethod ctMethod = ctMethods[i];
// final和static修饰的方法不能被继承
if ((ctMethod.getModifiers() & Modifier.FINAL) == Modifier.FINAL
|| (ctMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
continue;
}
// 满足指定的修饰符
int modifyFlag = -1;
if (isPublic) {
// public 方法
if ((ctMethod.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC) {
modifyFlag = Modifier.PUBLIC;
}
} else {
// protected 方法
if ((ctMethod.getModifiers() & Modifier.PROTECTED) == Modifier.PROTECTED) {
modifyFlag = Modifier.PROTECTED;
} else if ((ctMethod.getModifiers() & Modifier.PUBLIC) == 0
&& (ctMethod.getModifiers() & Modifier.PROTECTED) == 0
&& (ctMethod.getModifiers() & Modifier.PRIVATE) == 0) {
modifyFlag = 0;
}
}
if (modifyFlag == -1) {
continue;
}
// 匹配对应的方法
int methodIndex = findSomeMethod(methods, ctMethod);
if (methodIndex == -1) {
continue;
}
// 将这个方法作为字段保存,便于新增的方法能够访问原来的方法
String code = null;
if (isPublic) {
code = String.format("private static Method method%d = Class.forName(\"%s\").getMethods()[%d];",
methodNameIndex, targetCtClass.getName(), methodIndex);
} else {
code = String.format("private static Method method%d = Class.forName(\"%s\").getDeclaredMethods()[%d];",
methodNameIndex, targetCtClass.getName(), methodIndex);
}
CtField field = CtField.make(code, proxyCtClass);
proxyCtClass.addField(field);
CtMethod newCtMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(), ctMethod.getParameterTypes(), proxyCtClass);
// 区分静态与非静态,主要就是对象是否传null。注意这里必须用($r)转换类型,否则会发生类型转换失败的问题
if ((ctMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
code = String.format("return ($r)invocationHandler.invoke(null, method%d, $args);", methodNameIndex);
} else {
code = String.format("return ($r)invocationHandler.invoke(this, method%d, $args);", methodNameIndex);
}
newCtMethod.setBody(code);
newCtMethod.setModifiers(modifyFlag);
proxyCtClass.addMethod(newCtMethod);
methodNameIndex++;
}
return methodNameIndex;
}
/**
* 从 methods 找到等于 ctMethod 的下标索引并返回。找不到则返回 -1
*/
private static int findSomeMethod(Method[] methods, CtMethod ctMethod) {
for (int i = 0; i < methods.length; i++) {
if (equalsMethod(methods[i], ctMethod)) {
return i;
}
}
return -1;
}
/**
* 判断{@link Method} 和 {@link CtMethod} 是否相等。主要从方法名、返回值类型、参数类型三个维度判断
*/
private static boolean equalsMethod(Method method, CtMethod ctMethod) {
if (method == null && ctMethod == null) {
return true;
}
if (method == null || ctMethod == null) {
return false;
}
try {
if (method.getName().equals(ctMethod.getName())
&& method.getReturnType().getName().equals(ctMethod.getReturnType().getName())) {
Class<?>[] parameterTypes = method.getParameterTypes();
CtClass[] parameterTypesCt = ctMethod.getParameterTypes();
if (parameterTypes.length != parameterTypesCt.length) {
return false;
}
boolean equals = true;
for (int i = 0; i < parameterTypes.length; i++) {
if (!parameterTypes[i].getName().equals(parameterTypesCt[i].getName())) {
equals = false;
break;
}
}
return equals;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 将 item 和 array 合并到 merge
*/
private static <T> T[] merge(T[] merge, T item, T[] array) {
List<T> list = new ArrayList<>();
list.add(item);
Collections.addAll(list, array);
list.toArray(merge);
return merge;
}
}
4. 使用方式
使用方法跟JDK的方法类似,只是需要额外指定被代理对象实例化的构造器,内部会自动转换为代理对象的构造器,这是因为实体类可能会有多个构造器。
private static void proxyByJavassist(){
try {
Demo demo = new Demo();
Class clazz = Demo.class;
// 指定被代理对象的构造器,内部会自动转换为代理对象的构造器
Constructor constructor = clazz.getConstructor(new Class[]{});
Object[] constructorParam = new Object[]{};
// 指定方法回调的接口
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before:" + method.getName());
// 记住这儿是调用的被代理对象的方法,所以传参是 demo 而不是 proxy
method.setAccessible(true);
Object result = method.invoke(demo, args);
System.out.println("after:" + method.getName());
return result;
}
};
Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), invocationHandler, clazz, constructor, constructorParam);
// 分别测试 public、protected、default的方法
((Demo)proxy).publicDemo();
((Demo)proxy).protectDemo();
((Demo)proxy).defaultDemo();
// 测试继承的public方法
System.out.println(proxy.toString());
} catch (Exception e) {
e.printStackTrace();
}
}