Java cglib動(dòng)態(tài)代理原理分析
本文分下面三個(gè)部分來(lái)分析cglib動(dòng)態(tài)代理的原理。
- cglib 動(dòng)態(tài)代理示例
- 代理類(lèi)分析
- Fastclass 機(jī)制分析
一、cglib 動(dòng)態(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方法來(lái)調(diào)用目標(biāo)類(lèi)的方法 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"); //實(shí)例化一個(gè)增強(qiáng)器,也就是cglib中的一個(gè)class generator Enhancer eh = new Enhancer(); //設(shè)置目標(biāo)類(lèi) eh.setSuperclass(Target.class); // 設(shè)置攔截對(duì)象 eh.setCallback(new Interceptor()); // 生成代理類(lèi)并返回一個(gè)實(shí)例 Target t = (Target) eh.create(); t.f(); t.g(); } }
運(yùn)行結(jié)果為:
I am intercept begin
Target f()
I am intercept end
I am intercept begin
Target g()
I am intercept end
與JDK動(dòng)態(tài)代理相比,cglib可以實(shí)現(xiàn)對(duì)一般類(lèi)的代理而無(wú)需實(shí)現(xiàn)接口。在上例中通過(guò)下列步驟來(lái)生成目標(biāo)類(lèi)Target的代理類(lèi):
- 創(chuàng)建Enhancer實(shí)例
- 通過(guò)setSuperclass方法來(lái)設(shè)置目標(biāo)類(lèi)
- 通過(guò)setCallback 方法來(lái)設(shè)置攔截對(duì)象
- create方法生成Target的代理類(lèi),并返回代理類(lèi)的實(shí)例
二、代理類(lèi)分析
在示例代碼中我們通過(guò)設(shè)置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY的屬性值來(lái)獲取cglib生成的代理類(lèi)。通過(guò)之前分析的命名規(guī)則我們可以很容易的在F:\\code下面找到生成的代理類(lèi) Target$$EnhancerByCGLIB$$788444a0.class 。
使用jd-gui進(jìn)行反編譯(由于版本的問(wèn)題,此處只能顯示部分代碼,可以結(jié)合javap的反編譯結(jié)果來(lái)進(jìn)行分析),由于cglib會(huì)代理Object中的finalize,equals, toString,hashCode,clone方法,為了清晰的展示代理類(lèi)我們省略這部分代碼,反編譯的結(jié)果如下:
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(); } } }
代理類(lèi)(Target$$EnhancerByCGLIB$$788444a0)繼承了目標(biāo)類(lèi)(Target),至于代理類(lèi)實(shí)現(xiàn)的factory接口與本文無(wú)關(guān),殘忍無(wú)視。代理類(lèi)為每個(gè)目標(biāo)類(lèi)的方法生成兩個(gè)方法,例如針對(duì)目標(biāo)類(lèi)中的每個(gè)非private方法,代理類(lèi)會(huì)生成兩個(gè)方法,以g方法為例:一個(gè)是@Override的g方法,一個(gè)是CGLIB$g$0(CGLIB$g$0相當(dāng)于目標(biāo)類(lèi)的g方法)。我們?cè)谑纠a中調(diào)用目標(biāo)類(lèi)的方法t.g()時(shí),實(shí)際上調(diào)用的是代理類(lèi)中的g()方法。接下來(lái)我們著重分析代理類(lèi)中的g方法,看看是怎么實(shí)現(xiàn)的代理功能。
當(dāng)調(diào)用代理類(lèi)的g方法時(shí),先判斷是否已經(jīng)存在實(shí)現(xiàn)了MethodInterceptor接口的攔截對(duì)象,如果沒(méi)有的話(huà)就調(diào)用CGLIB$BIND_CALLBACKS方法來(lái)獲取攔截對(duì)象,CGLIB$BIND_CALLBACKS的反編譯結(jié)果如下:
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
為了方便閱讀,等價(jià)的代碼如下:
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中g(shù)et攔截對(duì)象,如果獲取不到的話(huà),再?gòu)腃GLIB$STATIC_CALLBACKS來(lái)獲取,如果也沒(méi)有則認(rèn)為該方法不需要代理。
那么攔截對(duì)象是如何設(shè)置到CGLIB$THREAD_CALLBACKS 或者 CGLIB$STATIC_CALLBACKS中的呢?
在Jdk動(dòng)態(tài)代理中攔截對(duì)象是在實(shí)例化代理類(lèi)時(shí)由構(gòu)造函數(shù)傳入的,在cglib中是調(diào)用Enhancer的firstInstance方法來(lái)生成代理類(lèi)實(shí)例并設(shè)置攔截對(duì)象的。firstInstance的調(diào)用軌跡為:
- Enhancer:firstInstance
- Enhancer:createUsingReflection
- Enhancer:setThreadCallbacks
- Enhancer:setCallbacksHelper
- Target$$EnhancerByCGLIB$$788444a0 : CGLIB$SET_THREAD_CALLBACKS
在第5步,調(diào)用了代理類(lèi)的CGLIB$SET_THREAD_CALLBACKS來(lái)完成攔截對(duì)象的注入。下面我們看一下CGLIB$SET_THREAD_CALLBACKS的反編譯結(jié)果:
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方法來(lái)保存攔截對(duì)象,在CGLIB$BIND_CALLBACKS方法中使用了CGLIB$THREAD_CALLBACKS的get方法來(lái)獲取攔截對(duì)象,并保存到CGLIB$CALLBACK_0中。這樣,在我們調(diào)用代理類(lèi)的g方法時(shí),就可以獲取到我們?cè)O(shè)置的攔截對(duì)象,然后通過(guò) tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy) 來(lái)實(shí)現(xiàn)代理。這里來(lái)解釋一下intercept方法的參數(shù)含義:
@para1 obj :代理對(duì)象本身
@para2 method : 被攔截的方法對(duì)象
@para3 args:方法調(diào)用入?yún)?/p>
@para4 proxy:用于調(diào)用被攔截方法的方法代理對(duì)象
這里會(huì)有一個(gè)疑問(wèn),為什么不直接反射調(diào)用代理類(lèi)生成的(CGLIB$g$0)來(lái)間接調(diào)用目標(biāo)類(lèi)的被攔截方法,而使用proxy的invokeSuper方法呢?這里就涉及到了另外一個(gè)點(diǎn)— FastClass 。
三、Fastclass 機(jī)制分析
Jdk動(dòng)態(tài)代理的攔截對(duì)象是通過(guò)反射的機(jī)制來(lái)調(diào)用被攔截方法的,反射的效率比較低,所以cglib采用了FastClass的機(jī)制來(lái)實(shí)現(xiàn)對(duì)被攔截方法的調(diào)用。FastClass機(jī)制就是對(duì)一個(gè)類(lèi)的方法建立索引,通過(guò)索引來(lái)直接調(diào)用相應(yīng)的方法,下面用一個(gè)小例子來(lái)說(shuō)明一下,這樣比較直觀
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中有兩個(gè)方法getIndex和invoke。在getIndex方法中對(duì)Test的每個(gè)方法建立索引,并根據(jù)入?yún)ⅲǚ椒?方法的描述符)來(lái)返回相應(yīng)的索引。Invoke根據(jù)指定的索引,以ol為入?yún)⒄{(diào)用對(duì)象O的方法。這樣就避免了反射調(diào)用,提高了效率。代理類(lèi)(Target$$EnhancerByCGLIB$$788444a0)中與生成Fastclass相關(guān)的代碼如下:
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中會(huì)對(duì)localClass1和localClass2進(jìn)行分析并生成FastClass,然后再使用getIndex來(lái)獲取方法g 和 CGLIB$g$0的索引,具體的生成過(guò)程將在后續(xù)進(jìn)行介紹,這里介紹一個(gè)關(guān)鍵的內(nèi)部類(lè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);
當(dāng)調(diào)用invokeSuper方法時(shí),實(shí)際上是調(diào)用代理類(lèi)的CGLIB$g$0方法,CGLIB$g$0直接調(diào)用了目標(biāo)類(lèi)的g方法。所以,在第一節(jié)示例代碼中我們使用invokeSuper方法來(lái)調(diào)用被攔截的目標(biāo)類(lèi)方法。
至此,我們已經(jīng)了解cglib動(dòng)態(tài)代理的工作原理,接下來(lái)會(huì)對(duì)cglib的相關(guān)源碼進(jìn)行分析。
以上就是Java cglib動(dòng)態(tài)代理原理分析的詳細(xì)內(nèi)容,更多關(guān)于Java cglib動(dòng)態(tài)代理原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 一文了解Java動(dòng)態(tài)代理的原理及實(shí)現(xiàn)
- Java實(shí)現(xiàn)JDK動(dòng)態(tài)代理的原理詳解
- Java JDK動(dòng)態(tài)代理實(shí)現(xiàn)原理實(shí)例解析
- Java動(dòng)態(tài)代理語(yǔ)法Proxy類(lèi)原理詳解
- Java 動(dòng)態(tài)代理原理分析
- Java JDK動(dòng)態(tài)代理(AOP)的實(shí)現(xiàn)原理與使用詳析
- JAVA動(dòng)態(tài)代理模式(從現(xiàn)實(shí)生活角度理解代碼原理)
- Java JDK動(dòng)態(tài)代理的基本原理詳細(xì)介紹
- Java動(dòng)態(tài)代理簡(jiǎn)單介紹
相關(guān)文章
SpringBoot集成Solr實(shí)現(xiàn)全文檢索功能
solr是一個(gè)現(xiàn)成的全文檢索引擎系統(tǒng), 放入tomcat下可以獨(dú)立運(yùn)行, 對(duì)外通過(guò)http協(xié)議提供全文檢索服務(wù),這篇文章給大家介紹了SpringBoot集成Solr實(shí)現(xiàn)全文檢索功能,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2024-03-03shiro與spring集成基礎(chǔ)Hello案例詳解
這篇文章主要介紹了shiro與spring集成基礎(chǔ)Hello案例詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11簡(jiǎn)單了解java集合框架LinkedList使用方法
這篇文章主要介紹了簡(jiǎn)單了解java集合框架LinkedList使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08SpringBoot使用iText7實(shí)現(xiàn)將HTML轉(zhuǎn)成PDF并添加頁(yè)眉頁(yè)腳水印
這篇文章主要為大家詳細(xì)介紹了SpringBoot使用iText7實(shí)現(xiàn)將HTML轉(zhuǎn)成PDF并添加頁(yè)眉頁(yè)腳水印的相關(guān)知識(shí),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03簡(jiǎn)單幾步實(shí)現(xiàn)將Spring security4.x升級(jí)到5.x
這篇文章主要介紹了簡(jiǎn)單幾步實(shí)現(xiàn)將Spring security4.x升級(jí)到5.x方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08java字符串與日期類(lèi)型轉(zhuǎn)換的工具類(lèi)
這篇文章主要為大家詳細(xì)介紹了java字符串與日期類(lèi)型轉(zhuǎn)換的工具類(lèi),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Spring-data-redis操作redis cluster的示例代碼
這篇文章主要介紹了Spring-data-redis操作redis cluster的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-10-10SpringSecurity實(shí)現(xiàn)自定義登錄方式
本文介紹自定義登錄流程,包括自定義AuthenticationToken、AuthenticationFilter、AuthenticationProvider以及SecurityConfig配置類(lèi),詳細(xì)解析了認(rèn)證流程的實(shí)現(xiàn),為開(kāi)發(fā)人員提供了具體的實(shí)施指導(dǎo)和參考2024-09-09使用SpringBoot 工廠模式自動(dòng)注入到Map
這篇文章主要介紹了使用SpringBoot 工廠模式自動(dòng)注入到Map,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09