天天看點

設計模式【3.3】-- CGLIB動态代理源碼解讀

設計模式【3.3】-- CGLIB動态代理源碼解讀

CGLIB 是一個開源項目,一個強大高性能高品質的代碼生成庫,可以在運作期拓展 Java 類,實作 Java 接口等等。底層是使用一個小而快的位元組碼處理架構 ASM,進而轉換位元組碼和生成新的類。

理論上我們也可以直接用 ASM 來直接生成代碼,但是要求我們對 JVM 内部,class 檔案格式,以及位元組碼的指令集都很熟悉。

這玩意不在 JDK 的包裡面,需要自己下載下傳導入或者 Maven 坐标導入。

我選擇 <code>Maven</code> 導入, 加到 <code>pom.xml</code> 檔案:

<code>Student.java</code>:

<code>MyProxy.java</code>(代理類)

測試類(<code>Test.java</code>)

運作之後的結果是:

在我們選擇的檔案夾裡面,生成了代理類的代碼:

設計模式【3.3】-- CGLIB動态代理源碼解讀

我們先要代理的類,需要實作<code>MethodInterceptor</code>(方法攔截器)接口,這個接口隻有一個方法 <code>intercept</code>,參數分别是:

obj:需要增強的對象

method:需要攔截的方法

args:要被攔截的方法參數

proxy:表示要觸發父類的方法對象

再看回我們要建立代理類的方法 <code>enhancer.create()</code>,這個方法的意思:如果需要,生成一個新類,并使用指定的回調(如果有的話)來建立一個新的對象執行個體。使用超類的無參數構造函數。

主要的方法邏輯我們得看 <code>createHelper()</code>,除了校驗,就是調用 <code>KEY_FACTORY.newInstance()</code> 方法生成 <code>EnhancerKey</code>對象,<code>KEY_FACTORY</code> 是靜态 <code>EnhancerKey</code> 接口,<code>newInstance()</code>是接口裡面的一個方法,重點在<code>super.create(key)</code>裡面,調用的是父類的方法:

<code>AbstractClassGenerator</code> 是 <code>Enhancer</code> 的父類,<code>create(key)</code> 方法的主要邏輯是擷取類加載器,緩存擷取類加載資料,然後再反射構造對象,裡面有兩個創造執行個體對象的方法:

<code>fistInstance()</code>: 不應該在正常流中調用此方法。從技術上講,<code>{@link #wrapCachedClass(Class)}</code>使用<code>{@link EnhancerFactoryData}</code>作為緩存值,後者支援比普通的舊反射查找和調用更快的執行個體化。出于向後相容性的原因,這個方法保持不變:隻是以防它曾經被使用過。(我的了解是目前的邏輯不會走到這個分支,因為它比較忙,但是為了相容,這個case還儲存着),内部邏輯其實用的是<code>ReflectUtils.newInstance(type)</code>。

<code>nextInstance()</code>: 真正的建立代理對象的類

這個方法定義在<code>AbstractClassGenerator</code>,但是實際上是調用子類 <code>Enhancer</code>的實作,主要是通過擷取參數類型,參數,以及回調對象,用這些參數反射生成代理對象。

内部實作邏輯,調用的都是<code>ReflectUtils.newInstance()</code>, 參數種類不一樣:

跟進去到底,就是擷取構造器方法,反射方式構造代理對象,最終調用到的是 JDK 提供的方法:

打開它自動生成的代理類檔案看看,就會發現其實也是生成那些方法,加上了一些增強方法:

設計模式【3.3】-- CGLIB動态代理源碼解讀

生成的代理類繼承了原來的類:

看看生成的增強方法,其實是調用到 <code>intercept()</code>方法,這個方法由我們前面自己實作,是以就完成了代理對象增強的功能:

jdk 動态代理是利用攔截器加上反射生成了一個代理接口的匿名類,執行方法的時候交給 InvokeHandler 處理。CGLIB 動态代理是使用了 ASM架構,修改原來的位元組碼,然後生成新的子類來處理。

JDK 代理需要實作接口,但是CGLIB不強制。

在JDK1.6之前,cglib因為用了位元組碼生成技術,比反射效率高,但是之後jdk也進行了一些優化,效率上已經提升了。

【作者簡介】:

秦懷,公衆号【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:<code>Java源碼解析</code>,<code>JDBC</code>,<code>Mybatis</code>,<code>Spring</code>,<code>redis</code>,<code>分布式</code>,<code>劍指Offer</code>,<code>LeetCode</code>等,認真寫好每一篇文章,不喜歡标題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正确,但是我保證所寫的均經過實踐或者查找資料。遺漏或者錯誤之處,還望指正。

劍指Offer全部題解PDF

2020年我寫了什麼?

開源程式設計筆記

繼續閱讀