動態代理的意義在于生成一個占位(又稱代理對象),來代理真實對象,從而控制真實對象的訪問。
我們首先來談談什么是代理模式。假設客戶帶著需求去找公司,顯然不會直接和軟件工程師談,而是和商務談,此時客戶會認為商務就代表公司,客戶是通過商務去訪問軟件工程師的。我們就可以認為商務(代理對象)代理了軟件工程師(真實對象),因此,代理的作用就是,在真實對象訪問之前或者之后加入對應的邏輯,或者根據其他規則控制是否使用真實對象。
商務和軟件工程師是代理和被代理的關系,客戶是通過商務去訪問軟件工程師的。此時客戶就是程序中的調用者,商務就是代理對象,軟件工程師就是真實對象。我們需要在調用者調用對象之前產生一個代理對象,而這個代理對象需要和真實對象建立代理關系,所以代理必須分為兩個步驟:
- 代理對象和真實對象建立代理關系
- 實現代理對象的代理邏輯方法
在java中有多種動態代理技術,比如jdk、cglib、javassist、asm,其中最常用的動態代理技術有兩種:一種是jdk動態代理,這是jdk自帶的功能;另一種是cglib,這是第三方提供的一個技術。目前,spring常用jdk和cglib,而mybatis還使用了javassist。(cglib的底層原理是asm)
本文只討論兩種最常用的動態代理技術:jdk和cglib。在jdk動態代理中必須使用接口,而cglib不需要,所以使用cglib會更簡單一點。下面依次討論這兩種技術。
一.jdk動態代理
jdk動態代理是java.lang.reflect.*包提供的方式。它必須借助一個接口才能產生代理對象,所以先定義接口helloworld:
1
2
3
|
public interface helloworld { void sayhelloworld(); } |
然后提供實現類helloworldimpl來實現接口:
1
2
3
4
5
6
|
public class helloworldimpl implements helloworld { @override public void sayhelloworld(){ system.out.println( "hello world" ); } } |
這是最簡答的java接口和實現類的關系,此時可以開始動態代理了。按照我們之前的分析,先要建立代理對象和真實對象的關系,然后實現代理邏輯,所以一共分為兩個步驟。在jdk動態代理中,代理邏輯類必須去實現java.lang.reflect.invocationhandler
接口,它定義了一個invoke方法,并提供接口數組用于下掛代理對象。我們自己定義一個jdk動態代理的類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public class myjdkproxyexample implements invocationhandler{ /** * @description 真實對象 **/ private object target = null ; /** * @author haozz * @date 2018-05-21 10:54 * @param target 真實對象 * @return 代理對象 * @throws * @description 建立代理對象和真實對象的代理關系,并返回代理對象 **/ public object getproxy(object target) { this .target = target; return proxy.newproxyinstance(target.getclass().getclassloader(),target.getclass().getinterfaces(), this ); } /** * @author haozz * @date 2018-05-21 11:09 * @param proxy 代理對象 * @param method 當前調度方法 * @param args 當前方法參數 * @return 代理結果 * @throws throwable 異常 * @description 代理方法邏輯 **/ @override public object invoke(object proxy, method method,object [] args) throws throwable{ system.out.println( "進入代理邏輯方法" ); system.out.println( "在調度真實對象之前的服務" ); object obj = method.invoke(target,args); //相當于調用sayhelloworld方法 system.out.println( "在調度真實對象之后的服務" ); return obj; } } |
第一步,通過getproxy()
方法建立代理對象和真實對象的關系,并返回代理對象。首先用類的屬性target保存了真實對象,然后通過proxy.newproxyinstance()
方法建立并生成代理對象,該方法中包含3個參數:
- 第1個是類加載器,這里采用了target本身的類加載器;
- 第2個是把生成的動態代理對象下掛在哪些接口下,這個寫法就是放在target實現的接口下。helloworldimpl對象的接口顯然就是helloworld,代理對象可以這樣聲明:helloworld proxy = xxxx;;
- 第3個是定義實現方法邏輯的代理類,this表示當前對象,它必須實現invocationhandler接口的invoke方法,它就是代理邏輯方法的現實方法。
第二步,通過invoke方法實現代理邏輯方法。invoke方法的3個參數:
- proxy,代理對象,就是getproxy方法生成的對象;
- method,當前調度的方法;
- args,當前調度方法的參數。
當我們使用了代理對象調度方法后,它就會進入到invoke方法里面。
1
|
object obj = method.invoke(target,args); |
這行代碼相當于調度真實對象的方法,只是通過反射實現。類比前面的例子,proxy相當于商務,target相當于軟件工程師,getproxy方法就是建立商務和軟件工程師之間的代理關系,invoke方法就是商務邏輯。
測試jdk動態代理:
1
2
3
4
5
6
7
8
|
@test public void testjdkproxy(){ myjdkproxyexample jdkproxy = new myjdkproxyexample(); //綁定關系,因為掛在接口helloworld下,所以聲明代理對象helloworld proxy helloworld proxy = (helloworld) jdkproxy.getproxy( new helloworldimpl()); //此時helloworld對象已經是一個代理對象,它會進入代理的邏輯方法invoke里 proxy.sayhelloworld(); } |
首先通過getproxy方法綁定了代理關系,然后在代理對象調度sayhelloworld方法時進入了代理的邏輯,測試結果如下:
進入代理邏輯方法
在調度真實對象之前的服務
hello world
在調度真實對象之后的服務
此時,在調度打印hello world之前和之后都可以加入相關的邏輯,甚至可以不調度hello world的打印。
個人小結:jdk動態代理要求真實對象和代理對象之間是實現類和接口的關系,創建一個代理邏輯類實現invocationhandler接口,其中getproxy方法用于生成代理對象,然后重寫invoke方法。
二.cglib動態代理
jdk動態代理必須提供接口才能使用,在一些不能提供接口的環境中,只能采用其他第三方技術,比如cglib動態代理,這里提供cglib動態代理的相關jar包,供學習和測試使用。它的優勢在于不需要提供接口,只要一個非抽象類就能實現動態代理。
cglib原理:動態生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法。在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。它比使用java反射的jdk動態代理要快。cglib底層:使用字節碼處理框架asm,來轉換字節碼并生成新的類。不鼓勵直接使用asm,因為它要求你必須對jvm內部結構包括class文件的格式和指令集都很熟悉。cglib缺點:對于final方法,無法進行代理。cglib廣泛地被許多aop的框架使用,例如spring aop和dynaop。hibernate使用cglib來代理單端single-ended(多對一和一對一)關聯。
我們以下面這個類為例:
1
2
3
4
5
|
public class reflectserviceimpl { public void sayhello(string name){ system.out.println( "hello " +name); } } |
它不存在實現任何接口,所以不能使用jdk動態代理,這里采用cglib動態代理技術:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public class mycglibproxyexample implements methodinterceptor{ /** * @author haozz * @date 2018-05-21 15:11 * @param cls class類 * @return class類的cglib對象 * @throws * @description 生成cglib代理對象 **/ public object getproxy( class cls){ //cglib增強類對象 enhancer enhancer = new enhancer(); //設置增強類型 enhancer.setsuperclass(cls); //定義代理邏輯對象為當前對象,要求當前對象實現methodinterceptor方法 enhancer.setcallback( this ); //生成并返回代理對象 return enhancer.create(); } /** * @author haozz * @date 2018-05-21 15:17 * @param proxy 代理對象 * @param method 目標方法 * @param args 目標方法參數 * @param methodproxy 方法代理 * @return 代理邏輯返回 * @throws throwable 異常 * @description 代理邏輯方法 **/ @override public object intercept(object proxy, method method, object[] args, methodproxy methodproxy) throws throwable{ system.out.println( "調用真實對象前" ); //cglib反射調用真實對象方法 object result = methodproxy.invokesuper(proxy,args); system.out.println( "調用真實對象后" ); return result; } } |
這里用了cglib的加強者enhancer(net.sf.sglib.proxy.enhancer),通過設置超類的方法(setsuperclass),然后通過setcallback方法設置哪個類為它的代理類。其中,參數this表示當前對象,那就要求用this這個對象實現接口methodinterceptor(net.sf.sglib.proxy.methodinterceptor)的方法intercept,然后返回代理對象。那么此時當前類的intercept方法就是其代理邏輯方法,其參數內容見代碼注解,我們在反射真實對象方法前后進行了打印,cglib是通過如下代碼完成的:
1
|
object result = methodproxy.invokesuper(proxy,args); |
測試一下cglib動態代理:
1
2
3
4
5
6
|
@test public void testcglibproxy(){ mycglibproxyexample cglibproxy = new mycglibproxyexample(); reflectserviceimpl obj = (reflectserviceimpl) cglibproxy.getproxy(reflectserviceimpl. class ); obj.sayhello( "haozz" ); } |
得到結果:
調用真實對象前
hello haozz
調用真實對象后
個人小結:cglib動態代理相對簡單一些,通過字節碼技術為需要代理的類生成一個子類,它不需要提供接口,只需寫一個代理邏輯類并實現methodinterceptor接口,其中getproxy方法用于生成代理對象,然后重寫intercept方法即可。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對服務器之家的支持。如果你想了解更多相關內容請查看下面相關鏈接
原文鏈接:https://blog.csdn.net/hz_940611/article/details/80388691