轉:JDK動態代理為什麼必須用介面以及與CGLIB的對比
參考連結: JDK動態代理為什麼必須用介面以及與CGLIB的對比
文章中提到:試驗了JDK動態代理與CGLIB動態代理。從Spring的AOP框架介紹中得知對於使用介面的類,Spring使用JDK動態代理(原來做專案中試圖從Bean強制轉換為實現類,結果報錯,原來是這麼回事),沒有介面的就使用別的AOP框架aspectj,但這些都是依賴於Java位元組碼工具ASM生成一個原類的新類,呼叫Callback
文章主要內容如下:
但是JDK動態代理為什麼必須使用介面一直很疑惑,難道原理不是像ASM一樣修改位元組碼嗎?帶著這個疑問,開始看JDK的Proxy程式碼。使用JDK動態代理的程式碼程式碼
ITestBean tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(), tb.getClass().getInterfaces(), new TestBeanHander(tb));
於是從建立代理函式看起,即public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException ,
通過原始碼可以看到,這個類第一步生成一個代理類(注意,這裡的引數就是介面列表),
Class cl = getProxyClass(loader, interfaces);
然後通過代理類找到構造引數為InvocationHandler的建構函式並生成一個新類。
Constructor cons = cl.getConstructor(constructorParams);//這個有用,在後面細說
return (Object) cons.newInstance(new Object[] { h });
介面起什麼作用呢,於是又看getProxyClass方法的程式碼,這個原始碼很長,就不細說了。大致分為三段:
第一:驗證
第二:快取建立新類的結構,如果建立過,則直接返回。(注意:這裡的KEY就是介面列表)
第三:如果沒有建立過,則建立新類
建立程式碼如下
long num;
//獲得代理類數字標識
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
//獲得建立新類的類名$Proxy,包名為介面包名,但需要注意的是,如果有兩個介面而且不在同一個包下,也會報錯
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//呼叫class處理檔案生成類的位元組碼,根據介面列表建立一個新類,這個類為代理類,
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
//通過JNI介面,將Class位元組碼檔案定義一個新類
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
根據前面的程式碼Constructor cons = cl.getConstructor(constructorParams);
可以猜測到介面建立的新類proxyClassFile 不管採用什麼介面,都是以下結構
public class $Proxy1 extends Proxy implements 傳入的介面{
}
生成新類的看不到原始碼,不過猜測它的執行原理很有可能是如果類是Proxy的子類,則呼叫InvocationHandler進行方法的Invoke
到現在大家都應該明白了吧,JDK動態代理的原理是根據定義好的規則,用傳入的介面建立一個新類,這就是為什麼採用動態代理時為什麼只能用介面引用指向代理,而不能用傳入的類引用執行動態類。
cglib採用的是用建立一個繼承實現類的子類,用asm庫動態修改子類的程式碼來實現的,所以可以用傳入的類引用執行代理類
JDK動態代理與CGLIB對比如下:
//JDK動態代理測試程式碼
ITestBean tb = new TestBean();
tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(), tb.getClass().getInterfaces(), new TestBeanHander(tb));//這句用介面引用指向,不會報錯
TestBean tmp = (TestBean) tb;//強制轉換為實現類,將丟擲類強制轉換異常
//CGLIB測試程式碼
TestProxy tp = new TestProxy();
tb = (ITestBean) tp.getProxy(TestBean.class);
tmp = (TeatBean) tb;//強制轉換為實現類,不會丟擲異常
補充說明,如果在實現類中,介面定義的方法互相呼叫不會在呼叫InvocationHandler的invoke方法,JDK動態代理應該不是嵌入到Java的反射機制中,而是在反射機制上的一個呼叫
---------------------
作者:magicianliu
來源:CSDN
原文:https://blog.csdn.net/MagicianLiu/article/details/4107497
版權宣告:本文為博主原創文章,轉載請附上博文連結!