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

轉: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
版權宣告:本文為博主原創文章,轉載請附上博文連結!