1. 程式人生 > >轉:JDK動態代理為什麽必須用接口以及與CGLIB的對比

轉:JDK動態代理為什麽必須用接口以及與CGLIB的對比

length exceptio cati class 疑惑 定義 實現類 tails ext

參考鏈接: 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
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

轉:JDK動態代理為什麽必須用接口以及與CGLIB的對比