背景
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 編譯優化