天天看點

CGLib 簡析

背景

 JDK 動态代理存在的一些問題:

 JDK 通過反射實作動态代理調用,這意味着低下的調用效率:

每次調用 <code>Method.invoke()</code> 都會檢查方法的可見性、校驗參數是否比對,過程涉及到很多 native 調用,具體參見 JNI 調用開銷

反射調用涉及動态類解析,這種不可預測性,導緻被反射調用的代碼無法被 JIT 内聯優化,具體參見 反射調用方法

 可以通過<code>java.lang.invoke.MethodHandle</code>來規避以上問題,但是這不在本文讨論的範圍。

 <code>java.lang.reflect.Proxy</code>隻支援通過接口生成代理類,這意味着 JDK 動态代理隻能代理接口,無法代理具體的類。

 對于一些外部依賴或者現有子產品來說,無法通過該方式實作動态代理。

應用場景

 CGLib 是一款用于實作高效動态代理的位元組碼增強庫,通過位元組碼生成技術,動态編譯生成代理類,進而将反射調用轉換為普通的方法調用。

 下面通過兩個案例體驗一下 CGLib 的使用方式。

 假設一個輸出問候語句的類 <code>Greet</code>,現在有個新需求:在輸出内容前後加上姓名,實作個性化輸出。下面通過 CGLib 實作該功能:

 随着業務發展,系統需要支援法語的問候 <code>FranceGreet</code>,在不修改現有業務代碼的前提下,可以通過 CGLib 實作該功能:

原理簡析

 從前面的案例可以看到,CGLib 使用的方式很簡單,大緻可以分為兩步:

配置 Enhancer

設定需要代理的目标類與接口

通過 <code>Callback</code> 設定需要增強的功能

通過 <code>CallbackFilter</code> 将方法比對到具體的 <code>Callback</code>

建立代理對象

通過 <code>CallbackFilter</code> 擷取方法與 <code>Callback</code> 的關聯關系

繼承目标類并重寫<code>override</code>方法,在調用代碼中嵌入 Callback

編譯動态生成的位元組碼生成代理類

通過反射調用構造函數生成代理對象

 此外,CGLib 支援多種 Callback,這裡簡單介紹幾種:

<code>NoOp</code> 不使用動态代理,比對到的方法不會被重寫

<code>FixedValue</code> 傳回固定值,被代理方法的傳回值被忽略

<code>Dispatcher</code> 指定上下文,将代理方法調用委托給特定對象

<code>MethodInterceptor</code> 調用攔截器,用于實作環繞通知<code>around advice</code>

 其中 <code>MethodInterceptor</code> 最為常用,可以實作多種豐富的代理特性。

 但這類 <code>Callback</code> 也是其中最重的,會導緻生成更多的動态類,具體原因後續介紹。

 底層通過 <code>Enhancer.generateClass()</code> 生成代理類,其具體過程不作深究,可以簡單概括為:

通過<code>ClassVisitor</code>擷取目标類資訊

通過<code>ClassEmitter</code>調用 asm 庫注入增強方法,并生成<code>byte[]</code> 形式的位元組碼

通過反射調用<code>ClassLoader.defineClass()</code>将<code>byte[]</code>轉換為<code>Class</code>對象

将生成完成的代理類緩存至<code>LoadingCache</code>,避免重複生成

 通過 arthas 的 jad 指令可以觀察到,案例 Weaving 中實際生成了以下類:

目标類:buttercup.test.Greet

代理類:buttercup.test.Greet$$EnhancerByCGLIB(省略字尾)

目标類 FastClass:buttercup.test.Greet$$FastClassByCGLIB(省略字尾)

代理類 FastClass:buttercup.test.Greet$$EnhancerByCGLIB$$FastClassByCGLIB(省略字尾)

 代理類就是 <code>Ehancer.create()</code> 中為了建立代理對象動态生成的類,該類不但繼承了目标類,并且還重寫了需要被代理的方法。其命名規則為:目标類 + $$EnhancerByCGLIB。

 在案例一中,我們分别給 <code>Greet.hello()</code> 與 <code>Greet.hi()</code> 分别添加了攔截器<code>Weaving.adviceBefore</code> 與 <code>Weaving.adviceAfter</code>,下面我們分析代理類是如何完成這一功能的:

 在動态生成的類中,CGLib 為每個被代理的方法建立了 <code>MethodProxy</code> 對象。

 該對象替代了 <code>Method.invoke()</code> 功能,是實作高效方法調用的的關鍵。下面我們以 <code>Greet.hello()</code> 為例對該類進行分析:

 可以看到 <code>MethodProxy</code> 的調用實際是通過 <code>FastClass</code> 完成的,這是 CGLib 實作高性能反射調用的秘訣,下面來解析這個類的細節。

 為了規避反射帶來的性能消耗,CGLib 定義了 <code>FastClass</code> 來實作高效的方法調用,其主要職責有兩個

方法映射:解析 Class 對象并為每個 Constructor 與 Method 指定一個整數索引值 index

方法調用:通過 switch(index) 的方式,将反射調用轉化為寫死調用

其命名規則為:目标類 + $$FastClassByCGLIB。下面具體分析一下對目标類 <code>Greet</code> 對應的 <code>FastClass</code>:

 之前提及過:使用 <code>MethodInterceptor</code> 會比其他 <code>Callback</code> 生成更多的動态類,這是因為需要支援 <code>MethodProxy.invokeSuper()</code> 調用:

 <code>MethodProxy.invokeSuper()</code> 通過調用代理類中帶 <code>$CGLIB$</code> 字首的方法,繞過被重寫的代理方法,避免出現無限遞歸。

 為了保證調用效率,需要對代理類也生成 <code>FastClass</code>:

 案例 Introduction 中僅使用了 <code>Dispatcher</code>,是以隻生成了代理類,未使用到 <code>FastClass</code>:

 本文案例僅涉及 <code>MethodInterceptor</code> 與 <code>Dispatcher</code>,這兩個 Callback 也是 Spring AOP 實作的關鍵,後續将繼續分析相關的源碼實作。

JIT 編譯優化