Java cglib動態(tài)代理原理分析
本文分下面三個部分來分析cglib動態(tài)代理的原理。
- cglib 動態(tài)代理示例
- 代理類分析
- Fastclass 機制分析
一、cglib 動態(tài)代理示例
public class Target{
public void f(){
System.out.println("Target f()");
}
public void g(){
System.out.println("Target g()");
}
}
public class Interceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("I am intercept begin");
//Note: 此處一定要使用proxy的invokeSuper方法來調(diào)用目標類的方法
proxy.invokeSuper(obj, args);
System.out.println("I am intercept end");
return null;
}
}
public class Test {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code");
//實例化一個增強器,也就是cglib中的一個class generator
Enhancer eh = new Enhancer();
//設置目標類
eh.setSuperclass(Target.class);
// 設置攔截對象
eh.setCallback(new Interceptor());
// 生成代理類并返回一個實例
Target t = (Target) eh.create();
t.f();
t.g();
}
}
運行結果為:
I am intercept begin
Target f()
I am intercept end
I am intercept begin
Target g()
I am intercept end
與JDK動態(tài)代理相比,cglib可以實現(xiàn)對一般類的代理而無需實現(xiàn)接口。在上例中通過下列步驟來生成目標類Target的代理類:
- 創(chuàng)建Enhancer實例
- 通過setSuperclass方法來設置目標類
- 通過setCallback 方法來設置攔截對象
- create方法生成Target的代理類,并返回代理類的實例
二、代理類分析
在示例代碼中我們通過設置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY的屬性值來獲取cglib生成的代理類。通過之前分析的命名規(guī)則我們可以很容易的在F:\\code下面找到生成的代理類 Target$$EnhancerByCGLIB$$788444a0.class 。
使用jd-gui進行反編譯(由于版本的問題,此處只能顯示部分代碼,可以結合javap的反編譯結果來進行分析),由于cglib會代理Object中的finalize,equals, toString,hashCode,clone方法,為了清晰的展示代理類我們省略這部分代碼,反編譯的結果如下:
public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory
{
private Boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$g$0$Method;
private static final MethodProxy CGLIB$g$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$f$1$Method;
private static final MethodProxy CGLIB$f$1$Proxy;
static void CGLIB$STATICHOOK1()
{
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
Class localClass2;
Method[] tmp60_57 = ReflectUtils.findMethods(new String[] { "g", "()V", "f", "()V" }, (localClass2 = Class.forName("net.sf.cglib.test.Target")).getDeclaredMethods());
CGLIB$g$0$Method = tmp60_57[0];
CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");
CGLIB$f$1$Method = tmp60_57[1];
CGLIB$f$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "f", "CGLIB$f$1");
}
final void CGLIB$g$0()
{
super.g();
}
public final void g()
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
CGLIB$BIND_CALLBACKS(this);
tmp4_1 = this.CGLIB$CALLBACK_0;
}
if (this.CGLIB$CALLBACK_0 != null) {
tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
} else{
super.g();
}
}
}
代理類(Target$$EnhancerByCGLIB$$788444a0)繼承了目標類(Target),至于代理類實現(xiàn)的factory接口與本文無關,殘忍無視。代理類為每個目標類的方法生成兩個方法,例如針對目標類中的每個非private方法,代理類會生成兩個方法,以g方法為例:一個是@Override的g方法,一個是CGLIB$g$0(CGLIB$g$0相當于目標類的g方法)。我們在示例代碼中調(diào)用目標類的方法t.g()時,實際上調(diào)用的是代理類中的g()方法。接下來我們著重分析代理類中的g方法,看看是怎么實現(xiàn)的代理功能。
當調(diào)用代理類的g方法時,先判斷是否已經(jīng)存在實現(xiàn)了MethodInterceptor接口的攔截對象,如果沒有的話就調(diào)用CGLIB$BIND_CALLBACKS方法來獲取攔截對象,CGLIB$BIND_CALLBACKS的反編譯結果如下:
private static final void CGLIB$BIND_CALLBACKS(java.lang.Object); Code: 0: aload_0 1: checkcast #2; //class net/sf/cglib/test/Target$$EnhancerByCGLIB$$788444a0 4: astore_1 5: aload_1 6: getfield #212; //Field CGLIB$BOUND:Z 9: ifne 52 12: aload_1 13: iconst_1 14: putfield #212; //Field CGLIB$BOUND:Z 17: getstatic #24; //Field CGLIB$THREAD_CALLBACKS:Ljava/lang/ThreadLocal; 20: invokevirtual #215; //Method java/lang/ThreadLocal.get:()Ljava/lang/Object; 23: dup 24: ifnonnull 39 27: pop 28: getstatic #210; //Field CGLIB$STATIC_CALLBACKS:[Lnet/sf/cglib/proxy/Callback; 31: dup 32: ifnonnull 39 35: pop 36: goto 52 39: checkcast #216; //class "[Lnet/sf/cglib/proxy/Callback;" 42: aload_1 43: swap 44: iconst_0 45: aaload 46: checkcast #48; //class net/sf/cglib/proxy/MethodInterceptor 49: putfield #36; //Field CGLIB$CALLBACK_0:Lnet/sf/cglib/proxy/MethodInterceptor; 52: return
為了方便閱讀,等價的代碼如下:
private static final void CGLIB$BIND_CALLBACKS(Object o){
Target$$EnhancerByCGLIB$$788444a0 temp_1 = (Target$$EnhancerByCGLIB$$788444a0)o;
Object temp_2;
Callback[] temp_3
if(temp_1.CGLIB$BOUND == true){
return;
}
temp_1.CGLIB$BOUND = true;
temp_2 = CGLIB$THREAD_CALLBACKS.get();
if(temp_2!=null){
temp_3 = (Callback[])temp_2;
}
else if(CGLIB$STATIC_CALLBACKS!=null){
temp_3 = CGLIB$STATIC_CALLBACKS;
}
else{
return;
}
temp_1.CGLIB$CALLBACK_0 = (MethodInterceptor)temp_3[0];
return;
}
CGLIB$BIND_CALLBACKS 先從CGLIB$THREAD_CALLBACKS中get攔截對象,如果獲取不到的話,再從CGLIB$STATIC_CALLBACKS來獲取,如果也沒有則認為該方法不需要代理。
那么攔截對象是如何設置到CGLIB$THREAD_CALLBACKS 或者 CGLIB$STATIC_CALLBACKS中的呢?
在Jdk動態(tài)代理中攔截對象是在實例化代理類時由構造函數(shù)傳入的,在cglib中是調(diào)用Enhancer的firstInstance方法來生成代理類實例并設置攔截對象的。firstInstance的調(diào)用軌跡為:
- Enhancer:firstInstance
- Enhancer:createUsingReflection
- Enhancer:setThreadCallbacks
- Enhancer:setCallbacksHelper
- Target$$EnhancerByCGLIB$$788444a0 : CGLIB$SET_THREAD_CALLBACKS
在第5步,調(diào)用了代理類的CGLIB$SET_THREAD_CALLBACKS來完成攔截對象的注入。下面我們看一下CGLIB$SET_THREAD_CALLBACKS的反編譯結果:
public static void CGLIB$SET_THREAD_CALLBACKS(net.sf.cglib.proxy.Callback[]); Code: 0: getstatic #24; //Field CGLIB$THREAD_CALLBACKS:Ljava/lang/ThreadLocal; 3: aload_0 4: invokevirtual #207; //Method java/lang/ThreadLocal.set:(Ljava/lang/Object;)V 7: return
在CGLIB$SET_THREAD_CALLBACKS方法中調(diào)用了CGLIB$THREAD_CALLBACKS的set方法來保存攔截對象,在CGLIB$BIND_CALLBACKS方法中使用了CGLIB$THREAD_CALLBACKS的get方法來獲取攔截對象,并保存到CGLIB$CALLBACK_0中。這樣,在我們調(diào)用代理類的g方法時,就可以獲取到我們設置的攔截對象,然后通過 tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy) 來實現(xiàn)代理。這里來解釋一下intercept方法的參數(shù)含義:
@para1 obj :代理對象本身
@para2 method : 被攔截的方法對象
@para3 args:方法調(diào)用入?yún)?/p>
@para4 proxy:用于調(diào)用被攔截方法的方法代理對象
這里會有一個疑問,為什么不直接反射調(diào)用代理類生成的(CGLIB$g$0)來間接調(diào)用目標類的被攔截方法,而使用proxy的invokeSuper方法呢?這里就涉及到了另外一個點— FastClass 。
三、Fastclass 機制分析
Jdk動態(tài)代理的攔截對象是通過反射的機制來調(diào)用被攔截方法的,反射的效率比較低,所以cglib采用了FastClass的機制來實現(xiàn)對被攔截方法的調(diào)用。FastClass機制就是對一個類的方法建立索引,通過索引來直接調(diào)用相應的方法,下面用一個小例子來說明一下,這樣比較直觀
public class test10 {
public static void main(String[] args){
Test tt = new Test();
Test2 fc = new Test2();
int index = fc.getIndex("f()V");
fc.invoke(index, tt, null);
}
}
class Test{
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
class Test2{
public Object invoke(int index, Object o, Object[] ol){
Test t = (Test) o;
switch(index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
public int getIndex(String signature){
switch(signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1;
}
}
上例中,Test2是Test的Fastclass,在Test2中有兩個方法getIndex和invoke。在getIndex方法中對Test的每個方法建立索引,并根據(jù)入?yún)ⅲǚ椒?方法的描述符)來返回相應的索引。Invoke根據(jù)指定的索引,以ol為入?yún)⒄{(diào)用對象O的方法。這樣就避免了反射調(diào)用,提高了效率。代理類(Target$$EnhancerByCGLIB$$788444a0)中與生成Fastclass相關的代碼如下:
Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
localClass2 = Class.forName("net.sf.cglib.test.Target");
CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");
MethodProxy中會對localClass1和localClass2進行分析并生成FastClass,然后再使用getIndex來獲取方法g 和 CGLIB$g$0的索引,具體的生成過程將在后續(xù)進行介紹,這里介紹一個關鍵的內(nèi)部類:
private static class FastClassInfo
{
FastClass f1; // net.sf.cglib.test.Target的fastclass
FastClass f2; // Target$$EnhancerByCGLIB$$788444a0 的fastclass
int i1; //方法g在f1中的索引
int i2; //方法CGLIB$g$0在f2中的索引
}
MethodProxy 中invokeSuper方法的代碼如下:
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
當調(diào)用invokeSuper方法時,實際上是調(diào)用代理類的CGLIB$g$0方法,CGLIB$g$0直接調(diào)用了目標類的g方法。所以,在第一節(jié)示例代碼中我們使用invokeSuper方法來調(diào)用被攔截的目標類方法。
至此,我們已經(jīng)了解cglib動態(tài)代理的工作原理,接下來會對cglib的相關源碼進行分析。
以上就是Java cglib動態(tài)代理原理分析的詳細內(nèi)容,更多關于Java cglib動態(tài)代理原理的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot集成Solr實現(xiàn)全文檢索功能
solr是一個現(xiàn)成的全文檢索引擎系統(tǒng), 放入tomcat下可以獨立運行, 對外通過http協(xié)議提供全文檢索服務,這篇文章給大家介紹了SpringBoot集成Solr實現(xiàn)全文檢索功能,文中有詳細的代碼示例供大家參考,需要的朋友可以參考下2024-03-03
SpringBoot使用iText7實現(xiàn)將HTML轉成PDF并添加頁眉頁腳水印
這篇文章主要為大家詳細介紹了SpringBoot使用iText7實現(xiàn)將HTML轉成PDF并添加頁眉頁腳水印的相關知識,感興趣的小伙伴可以跟隨小編一起學習一下2024-03-03
簡單幾步實現(xiàn)將Spring security4.x升級到5.x
這篇文章主要介紹了簡單幾步實現(xiàn)將Spring security4.x升級到5.x方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
Spring-data-redis操作redis cluster的示例代碼
這篇文章主要介紹了Spring-data-redis操作redis cluster的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-10-10

