代理模式實現方式及優缺點對比


https://www.cnblogs.com/zhangxufeng/p/9162182.html 

 代理模式最典型的應用就是AOP,本文結合主要講解了代理模式的幾種實現方式:靜態代理和動態代理,這里動態代理又可以分為jdk代理和Cglib代理,另外,本文也對這幾種代理模式的優缺點進行了對比。

       代理,顧名思義,即代替被請求者來處理相關事務。代理對象一般會全權代理被請求者的全部只能,客戶訪問代理對象就像在訪問被請求者一樣,雖然代理對象最終還是可能會訪問被請求者,但是其可以在請求之前或者請求之后進行一些額外的工作,或者說客戶的請求不合法,直接拒絕客戶的請求。如下圖所示為代理模式的一份簡圖:

代理模式的角色:

  • ISubject:代理者與被代理者共同實現的接口,可以理解為需要代理的行為;
  • SubjectImpl:被代理者,其為具有某種特定行為的實現者;
  • SubjectProxy:代理者,其會全權代理SubjectImpl所具有的功能,在實現其功能的基礎上做一些額外的工作;
  • Client:客戶端,客戶端訪問代理者與訪問被代理者具有類似的效果,其無法區分訪問的是代理者還是被代理者。

1. 靜態代理

       靜態代理模式也即上圖中描述的這種模式,從圖中可以看出,SubjectProxy保存一個ISubject實例,當客戶端調用SubjectProxy的request()方法時,其除了做額外的工作之外,還會調用ISubject實例的request()方法。如下是這三個類的一個簡單實現:

public interface ISubject { void request(); }
public class SubjectImpl implements ISubject { @Override public void request() { System.out.println("request SubjectImpl."); } }
public class SubjectProxy implements ISubject { private ISubject target; public SubjectProxy(ISubject target) { this.target = target; } @Override public void request() { System.out.println("before safety check."); target.request(); System.out.println("after safety check."); } }

       可以看到,代理對象在調用被代理對象的方法之前和之后都打印了相關的語句。如下是客戶端請求示例:

public class Client { @Test public void testStaticProxy() { ISubject subject = new SubjectImpl(); ISubject proxy = new SubjectProxy(subject); proxy.request(); } }

運行上述用例,可得到如下結果:

before safety check.
request SubjectImpl.
after safety check.

       從客戶端訪問方式可以看出,客戶端獲取的是一個實現ISubject接口的實例,其在調用的request()方法實際上是代理對象的request()方法。這種代理方式稱為靜態代理,並且這種代理方式也是效率最高的一種方式,因為所有的類都是已經編寫完成的,客戶端只需要取得代理對象並且執行即可。

       靜態代理雖然效率較高,但其也有不可避免的缺陷。可以看到,客戶端在調用代理對象時,使用的是代理對象和被代理對象都實現的一個接口,我們可以將該接口理解為定義了某一種業務需求的實現規范。如果有另外一份業務需求(如進行數據修改),其與當前需求並行的,沒有交集的,但是其在進行正常業務之外所做的安全驗證工作與當前需求是一致的。如下是我們進行該數據修改業務的實現代碼:

public interface IUpdatable { void update(); }
public class UpdatableImpl implements IUpdatable { @Override public void update() { System.out.println("update UpdatableImpl."); } }
public class UpdatableProxy implements IUpdatable { private IUpdatable updatable; public UpdatableProxy(IUpdatable updatable) { this.updatable = updatable; } @Override public void update() { System.out.println("pre safety check."); updatable.update(); System.out.println("after safety check."); } }

如下是客戶端代碼:

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(); } }

       可以看到,要實現相同的對象代理功能(安全驗證),靜態代理方式需要為每個接口實現一個代理類,而這些代理類中的代碼幾乎是一致的。這在大型系統中將會產生很大的維護問題。

2. 動態代理

① jdk代理

       所謂的jdk代理指的是借助jdk所提供的相關類來實現代理模式,其主要有兩個類:InvocationHandler和Proxy。在實現代理模式時,只需要實現InvocationHandler接口即可,如下是實現該接口的一個示例:

public class SafetyInvocationHandler implements InvocationHandler { private Object target; public SafetyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before safety check."); Object result = method.invoke(target, args); System.out.println("after safety check."); return result; } }

       如下是客戶端調用方式:

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(); } }

       可以看到,客戶端在調用代理對象時使用的都是同一個SafetyInvocationHandler。這里jdk代理其實在底層利用反射為每個需要代理的對象都創建了一個InvocationHandler實例,在調用目標對象時,其首先會調用代理對象,然后在代理對象的邏輯中請求目標對象。這也就是為什么在代理類中可以保存目標對象實例的原因,比如上述的SafetyInvocationHandler,其聲明了一個Object類型的屬性用來保存目標對象的實例。

       jdk代理解決了靜態代理需要為每個業務接口創建一個代理類的問題,雖然使用反射創建代理對象效率比靜態代理稍低,但其在現代高速jvm中也是可以接受的,在Spring的AOP代理中默認就是使用的jdk代理實現的。這里jdk代理的限制也是比較明顯的,即其需要被代理的對象必須實現一個接口。這里如果被代理對象沒有實現任何接口,或者被代理的業務方法沒有相應的接口,我們則可以使用另一種方式來實現,即Cglib代理。

② Cglib代理

       Cglib代理是功能最為強大的一種代理方式,因為其不僅解決了靜態代理需要創建多個代理類的問題,還解決了jdk代理需要被代理對象實現某個接口的問題。對於需要代理的類,如果能為其創建一個子類,並且在子類中編寫相關的代理邏輯,因為“子類 instanceof 父類”,因而在進行調用時直接調用子類對象的實例,也可以達到代理的效果。Cglib代理的原理實際上是動態生成被代理類的子類字節碼,由於其字節碼都是按照jvm編譯后的class文件的規范編寫的,因而其可以被jvm正常加載並運行。這也就是Cglib代理為什么不需要為每個被代理類編寫代理邏輯的原因。這里需要注意的是,根據Cglib實現原理,由於其是通過創建子類字節碼的形式來實現代理的,如果被代理類的方法被聲明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(); } }

       可以看到,客戶端代碼中首先創建了一個Enhancer對象,並且設置了父類及代理回調類對象。該Enhancer對象會為目標類創建相關的子類字節碼,並且將代理代碼植入該子類字節碼中。

3. 總結

       本文主要對代理模式的三種實現方式進行了詳細講解,並且比較了各個代理方式的優缺點,Spring主要使用的是動態代理方式實現切面編程的。這里讀者可能會有一個疑問,即上述代理代碼中,根據實現方式的不同,對客戶端代碼都有一定的侵入性,比如靜態代理客戶端需要侵入代理類的實例,jdk代理需要侵入Proxy類,而Cglib代理則需要侵入子類子類對象創建等代碼。理論上,客戶端只需要獲取目標對象,無論是否為代理過的,然后調用其相關方法實現特定功能即可。這其實也是工廠方法的強大之處,因為工廠方法會將對象的創建封裝起來,對象的具體創建過程可以根據具體的業務處理即可,客戶端只需要依賴工廠類調用相關的方法即可。同樣的這也就說明了Spring IoC容器是天然支持AOP代理的原因,因為其將對象的創建過程交由容器進行了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM