java中動(dòng)態(tài)代理如何實(shí)現(xiàn)詳解
Java實(shí)現(xiàn)動(dòng)態(tài)代理的兩種方式
- JDK動(dòng)態(tài)代理:Java.lang.reflect 包中的Proxy類和InvocationHandler接口提供了生成動(dòng)態(tài)代理類的能力。
- Cglib動(dòng)態(tài)代理:Cglib (Code Generation Library )是一個(gè)第三方代碼生成類庫,運(yùn)行時(shí)在內(nèi)存中動(dòng)態(tài)生成一個(gè)子類對(duì)象從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象功能的擴(kuò)展。
兩種動(dòng)態(tài)代理的區(qū)別
JDK 的動(dòng)態(tài)代理是基于接口的代理。
它要求被代理的類必須實(shí)現(xiàn)一個(gè)或多個(gè)接口。在運(yùn)行時(shí),JDK 動(dòng)態(tài)代理會(huì)根據(jù)被代理類實(shí)現(xiàn)的接口生成一個(gè)代理對(duì)象,該代理對(duì)象實(shí)現(xiàn)了被代理類的接口,并將方法的調(diào)用轉(zhuǎn)發(fā)給真正的被代理類。JDK 動(dòng)態(tài)代理的優(yōu)點(diǎn)是簡單易用,缺點(diǎn)是只能代理實(shí)現(xiàn)了接口的類。
CGLIB 是基于繼承的代理。
它可以代理沒有實(shí)現(xiàn)任何接口的類。在運(yùn)行時(shí),CGLIB 會(huì)動(dòng)態(tài)生成一個(gè)被代理類的子類(Cglib包的底層是通過使用一個(gè)小而快的字節(jié)碼處理框架ASM,來轉(zhuǎn)換字節(jié)碼并生成新的類。不鼓勵(lì)直接使用ASM,因?yàn)樗枰銓?duì)JVM內(nèi)部結(jié)構(gòu)包括class文件的格式和指令集都很熟悉。),并重寫父類中的方法,從而實(shí)現(xiàn)代理功能。CGLIB 的優(yōu)點(diǎn)是可以代理沒有實(shí)現(xiàn)接口的類,缺點(diǎn)是生成的代理類需要繼承被代理類,并且無法代理 final 類型的方法。它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口。它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。
總結(jié)
使用JDK動(dòng)態(tài)代理的對(duì)象必須實(shí)現(xiàn)一個(gè)或多個(gè)接口;而使用cglib代理的對(duì)象則無需實(shí)現(xiàn)接口,達(dá)到代理類無侵入。
JDK 的動(dòng)態(tài)代理和 CGLIB 都有各自的優(yōu)點(diǎn)和缺點(diǎn),具體使用哪種方式取決于具體的需求和場景。如果被代理的類已經(jīng)實(shí)現(xiàn)了接口,那么可以優(yōu)先考慮使用 JDK 的動(dòng)態(tài)代理;如果被代理的類沒有實(shí)現(xiàn)接口,或者需要對(duì)類的所有方法進(jìn)行代理,那么可以考慮使用 CGLIB。
補(bǔ)充
靜態(tài)代理和動(dòng)態(tài)代理的區(qū)別
最大的區(qū)別就是靜態(tài)代理是編譯期確定的,但是動(dòng)態(tài)代理卻是運(yùn)行期確定的。
同時(shí),使用靜態(tài)代理模式需要程序員手寫很多代碼,這個(gè)過程是比較浪費(fèi)時(shí)間和精力的。一旦需要代理的類中方法比較多,或者需要同時(shí)代理多個(gè)對(duì)象的時(shí)候,這無疑會(huì)增加很大的復(fù)雜度。
反射是動(dòng)態(tài)代理的實(shí)現(xiàn)方式之一。
動(dòng)態(tài)代理的用途
Java的動(dòng)態(tài)代理,在日常開發(fā)中可能并不經(jīng)常使用,但是并不代表他不重要。Java的動(dòng)態(tài)代理的最主要的用途就是應(yīng)用在各種框架中。因?yàn)槭褂脛?dòng)態(tài)代理可以很方便的運(yùn)行期生成代理類,通過代理類可以做很多事情,比如AOP,比如過濾器、攔截器等。
在我們平時(shí)使用的框架中,像servlet的filter、包括spring提供的aop以及struts2的攔截器都使用了動(dòng)態(tài)代理功能。我們?nèi)粘?吹降膍ybatis分頁插件,以及日志攔截、事務(wù)攔截、權(quán)限攔截這些幾乎全部由動(dòng)態(tài)代理的身影。
Spring AOP的實(shí)現(xiàn)方式
Spring AOP中的動(dòng)態(tài)代理主要有兩種方式,JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。
JDK動(dòng)態(tài)代理通過反射來接收被代理的類,并且要求被代理的類必須實(shí)現(xiàn)一個(gè)接口。JDK動(dòng)態(tài)代理的核心是InvocationHandler接口和Proxy類。
如果目標(biāo)類沒有實(shí)現(xiàn)接口,那么Spring AOP會(huì)選擇使用CGLIB來動(dòng)態(tài)代理目標(biāo)類。
CGLIB(Code Generation Library),是一個(gè)代碼生成的類庫,可以在運(yùn)行時(shí)動(dòng)態(tài)的生成某個(gè)類的子類,注意,CGLIB是通過繼承的方式做的動(dòng)態(tài)代理,因此如果某個(gè)類被標(biāo)記為final,那么它是無法使用CGLIB做動(dòng)態(tài)代理的。
JDK 動(dòng)態(tài)代理代碼示例
public class UserServiceImpl implements UserService { @Override public void add() { // TODO Auto-generated method stub System.out.println("--------------------add----------------------"); } } public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { super(); this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在方法調(diào)用前進(jìn)行性能監(jiān)控 PerformanceMonior.begin(target.getClass().getName()+"."+method.getName()); // 通過反射調(diào)用目標(biāo)對(duì)象的方法 Object result = method.invoke(target, args); // 在方法調(diào)用后結(jié)束性能監(jiān)控 PerformanceMonior.end(); return result; } public Object getProxy(){ return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this); } } public static void main(String[] args) { UserService service = new UserServiceImpl(); MyInvocationHandler handler = new MyInvocationHandler(service); UserService proxy = (UserService) handler.getProxy(); proxy.add(); }
代碼整體解讀:
說人話就是我們通過jdk的動(dòng)態(tài)代理,對(duì)用戶服務(wù)的新增用戶方法,追加了一個(gè)性能監(jiān)控功能,通過傳入原對(duì)象,得到代理對(duì)象(傳入原對(duì)象,我攔截開啟性能監(jiān)控功能,我再把方法的調(diào)用轉(zhuǎn)發(fā)(本質(zhì)就是反射機(jī)制調(diào)用)給原對(duì)象,原對(duì)象方法執(zhí)行結(jié)束,我結(jié)束性能監(jiān)控)。
Cglib動(dòng)態(tài)代理代碼示例
public class UserServiceImpl implements UserService { @Override public void add() { // TODO Auto-generated method stub System.out.println("--------------------add----------------------"); } } public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { // 設(shè)置需要?jiǎng)?chuàng)建子類的類 enhancer.setSuperclass(clazz); enhancer.setCallback(this); // 通過字節(jié)碼技術(shù)動(dòng)態(tài)創(chuàng)建子類實(shí)例 return enhancer.create(); } // 實(shí)現(xiàn)MethodInterceptor接口方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前置代理"); // 通過代理類調(diào)用父類中的方法 Object result = proxy.invokeSuper(obj, args); System.out.println("后置代理"); return result; } } public class DoCGLib { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); // 通過生成子類的方式創(chuàng)建代理類 UserServiceImpl proxyImp = (UserServiceImpl)proxy.getProxy(UserServiceImpl.class); proxyImp.add(); } }
總結(jié)
到此這篇關(guān)于java中動(dòng)態(tài)代理如何實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)java動(dòng)態(tài)代理實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java復(fù)制文件和java移動(dòng)文件的示例分享
本文主要介紹了java將文件夾下面的所有的jar文件拷貝到指定的文件夾下面的方法,需要的朋友可以參考下2014-02-02JAVA使用quartz添加定時(shí)任務(wù),并依賴注入對(duì)象操作
這篇文章主要介紹了JAVA使用quartz添加定時(shí)任務(wù),并依賴注入對(duì)象操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09java保證對(duì)象在內(nèi)存中唯一性的實(shí)現(xiàn)方法
這篇文章主要介紹了java如何保證對(duì)象在內(nèi)存中的唯一性,如果創(chuàng)建多個(gè)對(duì)象的話,可能會(huì)引發(fā)出各種各樣的問題,這時(shí),就需要我們保證這個(gè)對(duì)象在內(nèi)存中的唯一性,需要的朋友可以參考下2019-06-06mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限項(xiàng)目實(shí)踐
本文主要介紹了mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06java文件操作工具類實(shí)現(xiàn)復(fù)制文件和文件合并
這篇文章主要介紹了java文件操作工具類,類實(shí)現(xiàn)了復(fù)制文件和文件合并的功能,需要的朋友可以參考下2014-03-03