目錄
代理模式是常用的結構型設計模式之一,當無法直接訪問某個對象或訪問某個對象存在困難時可以通過一個代理對象來間接訪問,爲了保證客戶端使用的透明性,所訪問的真實對象與代理對象需要實現相同的接口。
根據代理模式的使用目的不同,代理模式又可以分爲多種類型,例如保護代理、遠程代理、虛擬代理、緩衝代理等,它們應用於不同的場合,滿足用戶的不同需求。
代理模式概述
代理模式是一種應用很廣泛的結構型設計模式。
代理模式定義如下:
代理模式(Proxy):爲其他對象提供一種代理以控制對這個對象的訪問。
代理模式是一種對象結構型模式。在代理模式中引入了一個新的代理對象,代理對象在客戶端對象和目標對象之間起到中介的作用,它去掉客戶不能看到的內容和服務或者增添客戶需要的額外的新服務。
所謂的代理者是指一個類別可以作爲其它東西的接口。代理者可以作任何東西的接口:網上連接、存儲器中的大對象、文件或其它昂貴或無法複製的資源。
代理模式結構與實現
模式結構
代理模式的結構比較簡單,其核心是代理類,爲了讓客戶端能夠一致性地對待真實對象和代理對象,在代理模式中引入了抽象層,代理模式結構如下圖所示:
代理模式包含如下三個角色:
- Subject:它聲明瞭真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程。
- Proxy:它包含了對真實主題的引用,從而可以在任何時候操作真實主題對象;在代理主題角色中提供一個與真實主題角色相同的接口,以便在任何時候都可以替代真實主題;代理主題角色還可以控制對真實主題的使用,負責在需要的時候創建和刪除真實主題對象,並對真實主題對象的使用加以約束。通常,在代理主題角色中,客戶端在調用所引用的真實主題操作之前或之後還需要執行其他操作,而不僅僅是單純調用真實主題對象中的操作。
- RealSubject:它定義了代理角色所代表的真實對象,在真實主題角色中實現了真實的業務操作,客戶端可以通過代理主題角色間接調用真實主題角色中定義的操作。
在實際開發過程中,代理類的實現比上述代碼要複雜很多,代理模式根據其目的和實現方式不同可分爲很多種類,其中常用的幾種代理模式簡要說明如下:
- 遠程代理(Remote Proxy):爲一個位於不同的地址空間的對象提供一個本地的代理對象,這個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中,遠程代理又稱爲大使(Ambassador)。
- 虛擬代理(Virtual Proxy):如果需要創建一個資源消耗較大的對象,先創建一個消耗相對較小的對象來表示,真實對象只在需要時纔會被真正創建。
- 保護代理(Protect Proxy):控制對一個對象的訪問,可以給不同的用戶提供不同級別的使用權限。
- 緩衝代理(Cache Proxy):爲某一個目標操作的結果提供臨時的存儲空間,以便多個客戶端可以共享這些結果。
- 智能引用代理(Smart Reference Proxy):當一個對象被引用時,提供一些額外的操作,例如將對象被調用的次數記錄下來等。
主要分爲靜態代理和動態代理兩類,動態代理分爲JDK動態代理和cglib代理
案例分析
靜態代理
subject類
public interface HelloService {
public void hello();
}
RealSubject類:
public class HelloServiceImpl implements HelloService {
@Override
public void hello() {
System.out.println("hello ");
}
}
Proxy類
public class HelloServiceProxy implements HelloService {
HelloService helloService;
public HelloServiceProxy(HelloService service){
this.helloService=service;
}
@Override
public void hello() {
System.out.println("before ");
helloService.hello();
System.out.println("end");
}
}
客戶端調用
public class Client {
public static void main(String[] args) {
HelloService helloService=new HelloServiceImpl();
HelloServiceProxy helloServiceProxy=new HelloServiceProxy(helloService);
helloServiceProxy.hello();
}
}
優點:可以做到在符合開閉原則的情況下對目標對象進行功能擴展。
缺點:我們得爲每一個服務都得創建代理類,工作量太大,不易管理。同時接口一旦發生改變,代理類也得相應修改。
JDK動態代理
proxy:
public class DynamicalProxy {
private HelloService helloService;
public DynamicalProxy(HelloService service){
this.helloService=service;
}
public Object getProxy(){
return Proxy.newProxyInstance(helloService.getClass().getClassLoader(), helloService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object o=method.invoke(helloService,args);
System.out.println("end");
return o;
}
});
}
}
注意Proxy.newProxyInstance()方法接受三個參數:
ClassLoader loader
:指定當前目標對象使用的類加載器,獲取加載器的方法是固定的Class<?>[] interfaces
:指定目標對象實現的接口的類型,使用泛型方式確認類型InvocationHandler:
指定
動態處理器,
執行目標對象的方法時,會觸發事件處理器的方法
客戶端調用
public class Client {
public static void main(String[] args) {
HelloService helloService=new HelloServiceImpl();
HelloService dynamicalProxy=(HelloService) new DynamicalProxy(helloService).getProxy();
dynamicalProxy.hello();
}
}
動態代理總結:雖然相對於靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務接口的依賴,降低了耦合度。但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持interface代理的桎梏。
Cglib代理
JDK實現動態代理需要實現類通過接口定義業務方法,對於沒有接口的類,如何實現動態代理呢,這就需要CGLib了。CGLib採用了非常底層的字節碼技術,其原理是通過字節碼技術爲一個類創建子類,並在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。但因爲採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
需要引入兩個jar包:cglib.jar,asm.jar
代理類
public class CglibProxy {
private Object o;
public CglibProxy(Object o) {
this.o = o;
}
public Object getProxy(){
//Enhancer類是cglib中的一個字節碼增強器,它可以方便的爲你所要處理的類進行擴展
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());//將目標對象所在的類作爲Enhaner類的父類
enhancer.setCallback(new MethodInterceptor() {
//通過實現MethodInterceptor實現方法回調
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
method.invoke(o, args);
System.out.println("end");
return proxy;
}
});
}
}
客戶端調用
public class Client {
public static void main(String[] args) {
HelloServiceImpl s=new HelloServiceImpl();
CglibProxy proxy=new CglibProxy(s);
HelloServiceImpl s1=(HelloServiceImpl) proxy.getProxy();
s1.hello();
}
}