利用代码分别实现jdk动态代理和cglib动态代理_代理模式实现方式及优缺点对比...
代理模式最典型的應(yīng)用就是AOP,本文結(jié)合主要講解了代理模式的幾種實現(xiàn)方式:靜態(tài)代理和動態(tài)代理,這里動態(tài)代理又可以分為jdk代理和Cglib代理,另外,本文也對這幾種代理模式的優(yōu)缺點進行了對比。
代理,顧名思義,即代替被請求者來處理相關(guān)事務(wù)。代理對象一般會全權(quán)代理被請求者的全部只能,客戶訪問代理對象就像在訪問被請求者一樣,雖然代理對象最終還是可能會訪問被請求者,但是其可以在請求之前或者請求之后進行一些額外的工作,或者說客戶的請求不合法,直接拒絕客戶的請求。如下圖所示為代理模式的一份簡圖:
代理模式的角色:
- ISubject:代理者與被代理者共同實現(xiàn)的接口,可以理解為需要代理的行為;
- SubjectImpl:被代理者,其為具有某種特定行為的實現(xiàn)者;
- SubjectProxy:代理者,其會全權(quán)代理SubjectImpl所具有的功能,在實現(xiàn)其功能的基礎(chǔ)上做一些額外的工作;
- Client:客戶端,客戶端訪問代理者與訪問被代理者具有類似的效果,其無法區(qū)分訪問的是代理者還是被代理者。
1. 靜態(tài)代理
靜態(tài)代理模式也即上圖中描述的這種模式,從圖中可以看出,SubjectProxy保存一個ISubject實例,當(dāng)客戶端調(diào)用SubjectProxy的request()方法時,其除了做額外的工作之外,還會調(diào)用ISubject實例的request()方法。如下是這三個類的一個簡單實現(xiàn):
可以看到,代理對象在調(diào)用被代理對象的方法之前和之后都打印了相關(guān)的語句。如下是客戶端請求示例:
public class Client { @Test public void testStaticProxy() { ISubject subject = new SubjectImpl(); ISubject proxy = new SubjectProxy(subject); proxy.request(); }}運行上述用例,可得到如下結(jié)果:
before safety check.request SubjectImpl.after safety check.從客戶端訪問方式可以看出,客戶端獲取的是一個實現(xiàn)ISubject接口的實例,其在調(diào)用的request()方法實際上是代理對象的request()方法。這種代理方式稱為靜態(tài)代理,并且這種代理方式也是效率最高的一種方式,因為所有的類都是已經(jīng)編寫完成的,客戶端只需要取得代理對象并且執(zhí)行即可。
靜態(tài)代理雖然效率較高,但其也有不可避免的缺陷??梢钥吹?#xff0c;客戶端在調(diào)用代理對象時,使用的是代理對象和被代理對象都實現(xiàn)的一個接口,我們可以將該接口理解為定義了某一種業(yè)務(wù)需求的實現(xiàn)規(guī)范。如果有另外一份業(yè)務(wù)需求(如進行數(shù)據(jù)修改),其與當(dāng)前需求并行的,沒有交集的,但是其在進行正常業(yè)務(wù)之外所做的安全驗證工作與當(dāng)前需求是一致的。如下是我們進行該數(shù)據(jù)修改業(yè)務(wù)的實現(xiàn)代碼:
如下是客戶端代碼:
public class Client { @Test public void testStaticProxy() { ISubject subject = new SubjectImpl(); ISubject proxy = new SubjectProxy(subject); proxy.request(); IUpdatable updatable = new UpdatableImpl(); IUpdatable proxy = new UpdatableProxy(updatable); proxy.update(); }}可以看到,要實現(xiàn)相同的對象代理功能(安全驗證),靜態(tài)代理方式需要為每個接口實現(xiàn)一個代理類,而這些代理類中的代碼幾乎是一致的。這在大型系統(tǒng)中將會產(chǎn)生很大的維護問題。
2. 動態(tài)代理
① jdk代理
所謂的jdk代理指的是借助jdk所提供的相關(guān)類來實現(xiàn)代理模式,其主要有兩個類:InvocationHandler和Proxy。在實現(xiàn)代理模式時,只需要實現(xiàn)InvocationHandler接口即可,如下是實現(xiàn)該接口的一個示例:
如下是客戶端調(diào)用方式:
public class Client { @Test public void testDynamicProxy() { ISubject subject = new SubjectImpl(); ISubject proxySubject = (ISubject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{ISubject.class}, new SafetyInvocationHandler(subject)); proxySubject.request(); IUpdatable updatable = new UpdatableImpl(); IUpdatable proxyUpdatable = (IUpdatable) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{IUpdatable.class}, new SafetyInvocationHandler(updatable)); proxyUpdatable.update(); }}可以看到,客戶端在調(diào)用代理對象時使用的都是同一個SafetyInvocationHandler。這里jdk代理其實在底層利用反射為每個需要代理的對象都創(chuàng)建了一個InvocationHandler實例,在調(diào)用目標(biāo)對象時,其首先會調(diào)用代理對象,然后在代理對象的邏輯中請求目標(biāo)對象。這也就是為什么在代理類中可以保存目標(biāo)對象實例的原因,比如上述的SafetyInvocationHandler,其聲明了一個Object類型的屬性用來保存目標(biāo)對象的實例。
jdk代理解決了靜態(tài)代理需要為每個業(yè)務(wù)接口創(chuàng)建一個代理類的問題,雖然使用反射創(chuàng)建代理對象效率比靜態(tài)代理稍低,但其在現(xiàn)代高速jvm中也是可以接受的,在Spring的AOP代理中默認就是使用的jdk代理實現(xiàn)的。這里jdk代理的限制也是比較明顯的,即其需要被代理的對象必須實現(xiàn)一個接口。這里如果被代理對象沒有實現(xiàn)任何接口,或者被代理的業(yè)務(wù)方法沒有相應(yīng)的接口,我們則可以使用另一種方式來實現(xiàn),即Cglib代理。
② Cglib代理
Cglib代理是功能最為強大的一種代理方式,因為其不僅解決了靜態(tài)代理需要創(chuàng)建多個代理類的問題,還解決了jdk代理需要被代理對象實現(xiàn)某個接口的問題。對于需要代理的類,如果能為其創(chuàng)建一個子類,并且在子類中編寫相關(guān)的代理邏輯,因為“子類 instanceof 父類”,因而在進行調(diào)用時直接調(diào)用子類對象的實例,也可以達到代理的效果。Cglib代理的原理實際上是動態(tài)生成被代理類的子類字節(jié)碼,由于其字節(jié)碼都是按照jvm編譯后的class文件的規(guī)范編寫的,因而其可以被jvm正常加載并運行。這也就是Cglib代理為什么不需要為每個被代理類編寫代理邏輯的原因。這里需要注意的是,根據(jù)Cglib實現(xiàn)原理,由于其是通過創(chuàng)建子類字節(jié)碼的形式來實現(xiàn)代理的,如果被代理類的方法被聲明final類型,那么Cglib代理是無法正常工作的,因為final類型方法不能被重寫。如下是使用Cglib代理的一個示例:
/** * 被代理類 */public class Suject { public void request() { System.out.println("update without implement any interface."); }}/** * 代理類 */public class SafetyCheckCallback implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before safety check."); Object result = methodProxy.invokeSuper(o, objects); System.out.println("after safety check."); return result; }}如下是客戶端訪問方式:
public class Client { @Test public void testCglibProxy() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Suject.class); enhancer.setCallback(new SafetyCheckCallback()); Suject proxy = (Suject) enhancer.create(); proxy.request(); }}可以看到,客戶端代碼中首先創(chuàng)建了一個Enhancer對象,并且設(shè)置了父類及代理回調(diào)類對象。該Enhancer對象會為目標(biāo)類創(chuàng)建相關(guān)的子類字節(jié)碼,并且將代理代碼植入該子類字節(jié)碼中。
3. 總結(jié)
本文主要對代理模式的三種實現(xiàn)方式進行了詳細講解,并且比較了各個代理方式的優(yōu)缺點,Spring主要使用的是動態(tài)代理方式實現(xiàn)切面編程的。這里讀者可能會有一個疑問,即上述代理代碼中,根據(jù)實現(xiàn)方式的不同,對客戶端代碼都有一定的侵入性,比如靜態(tài)代理客戶端需要侵入代理類的實例,jdk代理需要侵入Proxy類,而Cglib代理則需要侵入子類子類對象創(chuàng)建等代碼。理論上,客戶端只需要獲取目標(biāo)對象,無論是否為代理過的,然后調(diào)用其相關(guān)方法實現(xiàn)特定功能即可。這其實也是工廠方法的強大之處,因為工廠方法會將對象的創(chuàng)建封裝起來,對象的具體創(chuàng)建過程可以根據(jù)具體的業(yè)務(wù)處理即可,客戶端只需要依賴工廠類調(diào)用相關(guān)的方法即可。同樣的這也就說明了Spring IoC容器是天然支持AOP代理的原因,因為其將對象的創(chuàng)建過程交由容器進行了。
總結(jié)
以上是生活随笔為你收集整理的利用代码分别实现jdk动态代理和cglib动态代理_代理模式实现方式及优缺点对比...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos usb转网口_CentOS
- 下一篇: 如何用unit test测试contro