1. JDK動態(tài)代理
相信大家對JDK的動態(tài)代理非常熟悉了郭卫,這里簡單舉個例子說明一下
首先是定義一個接口霹疫,然后定義一個類并實現(xiàn)這個接口
public interface ICallback{
void callback();
}
public static class MyCallback implements ICallback {
@Override
public void callback() {
System.out.println("MyCallback");
}
}
接著用 JDK 的 Proxy 生成代理類舔腾,在每個方法調(diào)用前后都加上一句輸出
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();
}
上面的代碼很簡單,但是他的局限性很大:只能代理接口,而不能代理實體類
那么能不能自己實現(xiàn)一種動態(tài)代理來代理實體類呢,經(jīng)過我的實踐泌参,發(fā)現(xiàn)是可以的脆淹。需要用到 Javassist 這個工具
2. 實體類動態(tài)代理的分析
這篇文章不是專門講解 Javassist,有興趣的可以去了解一下沽一。Javassist官方文檔傳送門盖溺,感覺英文吃力的可以看國內(nèi)大牛翻譯的 Javassist文檔翻譯
這里我們只需要知道添加 javassist 的依賴
implementation 'org.javassist:javassist:3.20.0-GA'
在實現(xiàn)實體類的動態(tài)代理前,我們先要分析接口和實體類的區(qū)分
- 接口的方法默認(rèn)都是 public 的铣缠,所有實現(xiàn)接口的類默認(rèn)都會全部繼承所有接口烘嘱;而實體類的方法有 private、protected蝗蛙、public 和 default 的區(qū)別
- 實現(xiàn)接口可以直接使用默認(rèn)無參構(gòu)造函數(shù)蝇庭;而繼承實體類有多個構(gòu)造函數(shù)需要繼承,并且需要制定一個構(gòu)造函數(shù)來實例化代理對象
- 接口的方法都不是 final 的捡硅;而實體類的方法可能是 final 的
- 接口的方法都不是 static 的哮内;而實體類的方法可能是 static 的
再梳理一下,模仿JDK的動態(tài)代理的設(shè)計思路病曾,實現(xiàn)動態(tài)代理實體類所需要的步驟
- 定義 InvocationHandler 類牍蜂,用于代理對象的方法調(diào)用時的回調(diào)
- 根據(jù) classloader 和 class 判斷是否有緩存漾根,如果有則直接從緩存獲取泰涂。否則再次生成class并在同一個 classloader 加載的話會出現(xiàn)問題
- 判斷被代理對象是否是final的,不是final才進行下一步
- 用javassist新建一個類辐怕,包名和被代理類一致逼蒙,采用Proxy_前綴命名
- 設(shè)置代理類的修飾符和繼承關(guān)系
- 添加成員變量 InvocationHandler,便于后面方法調(diào)用時的回調(diào)
- 添加構(gòu)造器寄疏,規(guī)則是在被代理類的所有構(gòu)造器的基礎(chǔ)上是牢,添加 InvocationHandler 作為第一個參數(shù)
- 添加方法,規(guī)則是在被代理類的所有方法上陕截,篩選 public驳棱、projected、default 的方法农曲,方法體直接調(diào)用 InvocationHandler 的 invoke 方法
- 用 classloader 生成 class社搅,并放入緩存
- 根據(jù)被代理對象的構(gòu)造函數(shù),在參數(shù)前面加一個 InvocationHandler 參數(shù)來實例化代理對象
3. 實現(xiàn)實體類動態(tài)代理
首先模仿 JDK 實現(xiàn)一個代理方法的回調(diào)接口
public interface InvocationHandler {
/**
* @param proxy 動態(tài)生成的代理對象
* @param method 調(diào)用的方法
* @param args 調(diào)用的參數(shù)
* @return 該方法的返回值
* @throws Throwable
*/
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
然后就是代理器的實現(xiàn)
public class Proxy {
// 動態(tài)生成代理類的前綴
private static final String PROXY_CLASSNAME_PREFIX = "$Proxy_";
// 緩存容器乳规,防止生成同一個Class文件在同一個ClassLoader加載崩潰的問題
private static final Map<String, Class<?>> proxyClassCache = new HashMap<>();
/**
* 緩存已經(jīng)生成的代理類的Class形葬,key值根據(jù) 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);
}
/**
* 返回一個動態(tài)創(chuàng)建的代理類暮的,此類繼承自 targetClass
*
* @param classLoader 從哪一個ClassLoader加載Class
* @param invocationHandler 代理類中每一個方法調(diào)用時的回調(diào)接口
* @param targetClass 被代理對象
* @param targetConstructor 被代理對象的某一個構(gòu)造器笙以,用于決定代理對象實例化時采用哪一個構(gòu)造器
* @param targetParam 被代理對象的某一個構(gòu)造器的參數(shù),用于實例化構(gòu)造器
* @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));
// 設(shè)置描述符
proxyCtClass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
// 設(shè)置繼承關(guān)系
proxyCtClass.setSuperclass(targetCtClass);
// 添加構(gòu)造器
addConstructor(pool, proxyCtClass, targetCtClass);
// 添加方法
addMethod(proxyCtClass, targetCtClass, targetClass);
// 從指定ClassLoader加載Class
proxyClass = proxyCtClass.toClass(classLoader, null);
// 緩存
saveProxyClassCache(classLoader, targetClass, proxyClass);
// 輸出到文件保存冻辩,用于debug調(diào)試
// 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;
}
/**
* 生成代理類的類名生成規(guī)則
*/
private static String generateProxyClassName(Class<?> targetClass) {
return targetClass.getPackage().getName() + "." + PROXY_CLASSNAME_PREFIX + targetClass.getSimpleName();
}
/**
* 根據(jù)被代理類的構(gòu)造器猖腕,構(gòu)造代理類對象拆祈。代理類的所有構(gòu)造器都是被代理類構(gòu)造器前添加一個invocationHandler 參數(shù)
*
* @param proxyClass 代理類
* @param invocationHandler 代理類所有構(gòu)造器的第一個參數(shù)
* @param targetConstructor 被代理類的構(gòu)造器
* @param targetParam 被代理類的構(gòu)造器的參數(shù)
* @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);
}
/**
* 代理類添加構(gòu)造器,基于被代理類的構(gòu)造器倘感,在所有參數(shù)開頭添加一個 {@link InvocationHandler} 參數(shù)
*
* @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);
// 因為第一個參數(shù)指定為 InvocationHandler缘屹,所以super()的參數(shù)是從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);
}
// 添加構(gòu)造器方法
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 被代理類的方法數(shù)組
* @param ctMethods 被代理類的方法數(shù)組
* @param isPublic 是否是共有方法逻炊,是:只包含public方法互亮;否:包含projected和default方法
* @param methodNameIndex 新建方法的命名下標(biāo)
* @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;
}
// 匹配對應(yīng)的方法
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);
// 區(qū)分靜態(tài)與非靜態(tài)余素,主要就是對象是否傳null豹休。注意這里必須用($r)轉(zhuǎn)換類型,否則會發(fā)生類型轉(zhuǎn)換失敗的問題
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 的下標(biāo)索引并返回桨吊。找不到則返回 -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} 是否相等威根。主要從方法名、返回值類型视乐、參數(shù)類型三個維度判斷
*/
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的方法類似洛搀,只是需要額外指定被代理對象實例化的構(gòu)造器,內(nèi)部會自動轉(zhuǎn)換為代理對象的構(gòu)造器佑淀,這是因為實體類可能會有多個構(gòu)造器留美。
private static void proxyByJavassist(){
try {
Demo demo = new Demo();
Class clazz = Demo.class;
// 指定被代理對象的構(gòu)造器,內(nèi)部會自動轉(zhuǎn)換為代理對象的構(gòu)造器
Constructor constructor = clazz.getConstructor(new Class[]{});
Object[] constructorParam = new Object[]{};
// 指定方法回調(diào)的接口
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before:" + method.getName());
// 記住這兒是調(diào)用的被代理對象的方法伸刃,所以傳參是 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();
// 測試?yán)^承的public方法
System.out.println(proxy.toString());
} catch (Exception e) {
e.printStackTrace();
}
}