當直接訪問某些物件存在問題時,可以通過一個代理物件來間接訪問,為了保證客戶端使用的透明性,所訪問的真實物件與代理物件需要實作相同的介面,
模式動機
某些情況下,一個客戶不想或不能直接參考一個物件,此時可以通過一個稱之為代理的第三者實作間接參考,代理物件在客戶端和目標物件之間起到中介作用,并且可以通過代理物件去掉客戶不能看到的內容和添加客戶需要的額外服務,
模式定義
給某一個物件提供一個代理,并由代理物件控制對原物件的參考,代理模式的英文叫做 Proxy 或 Surrogate,它是一種物件結構模式,
模式結構

-
Subject(抽象主題角色)
宣告了真實主題和代理主題的公共介面,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端針對抽象主題角色編程,
-
Proxy(代理主題角色)
代理主題角色內部包含對真實主題的參考,從而可以在任何時候操作真實主題角色,
代理主題角色中提供一個與真實主題角色相同的介面,以便在任何時候替代真實主體,
代理主題角色還可以控制對真實主題的使用,負責在需要時創建和洗掉真實主題物件,并對真實主題物件的使用加以約束,
代理角色通常在客戶端呼叫所參考的真實主題操作之前或之后執行其他操作,而不僅僅只是單純呼叫真實主題物件中的操作,
-
RealSubject(真實主題角色)
真實主題角色定義了代理角色所代表的真實物件,真實主題角色中實作真實的業務,客戶端通過代理主題角色間接呼叫真實主題角色中定義的方法,
模式實體與決議
在一個論壇已注冊用戶和游客權限不同,已注冊用戶擁有發帖、修改注冊資訊、修改自己帖子等功能;而游客只能看到別人發的貼子,沒有其他權限,本實體中我們使用代理模式中的保護代理,該代理用于控制對一個物件的訪問,可以給不同用戶提供不同級別的使用權限,

-
抽象主題角色 AbstractPermission(抽象權限類)
AbstractPermission 作為抽象權限類,充當抽象主題角色,在其中宣告了真實主題角色所提供的業務方法,它是真實主題角色和代理主題角色的公共介面
public interface AbstractPermission { public void modifyUserInfo(); public void viewNote(); public void publishNote(); public void modifyNote(); public void setLevel(int level); } -
真實主題角色 RealPermission(真實權限類)
RealPermission 是真實主題角色,它實作了在抽象主題角色中定義的方法,由于種種原因客戶端無法訪問其中內容,
public class RealPermission implements AbstractPermission { @Override public void modifyUserInfo() { System.out.println("修改用戶資訊"); } @Override public void viewNote() { } @Override public void publishNote() { System.out.println("發布新帖"); } @Override public void modifyNote() { System.out.println("修改發帖內容"); } @Override public void setLevel(int level) { } } -
代理主題角色 PermissionProxy(權限代理類)
PermissionProxy 是代理主題角色,它也實作了抽象主題角色介面,同時在 PermissionProxy 中定義了一個 RealPermission 物件,用于呼叫 RealPermission 中定義的真實業務方法,通過引入 PermissionProxy 類來對系統的使用權限進行控制,這就是保護代理的用途,
public class PermissionProxy implements AbstractPermission { private RealPermission permission = new RealPermission(); private int level = 0; @Override public void modifyUserInfo() { if (0 == level) { System.out.println("對不起,你沒有該權限"); } else if (1 == level) { permission.modifyUserInfo(); } } @Override public void viewNote() { System.out.println("查看帖子"); } @Override public void publishNote() { if (0 == level) { System.out.println("對不起,你沒有該權限"); } else if (1 == level) { permission.publishNote(); } } @Override public void modifyNote() { if (0 == level) { System.out.println("對不起,你沒有該權限"); } else if (1 == level) { permission.modifyNote(); } } @Override public void setLevel(int level) { this.level = level; } } -
客戶端測驗類 Client
public class Client { public static void main(String[] args) { AbstractPermission permission = new PermissionProxy(); permission.modifyUserInfo(); permission.viewNote(); permission.publishNote(); permission.modifyNote(); System.out.println("-------------------------"); permission.setLevel(1); permission.modifyUserInfo(); permission.viewNote(); permission.publishNote(); permission.modifyNote(); } } -
運行結果

代理類可以對用戶訪問權限進行控制,因此有些用戶無權呼叫真實業務類的某些方法,當用戶權限改變時,則可以訪問這些方法,如果需要增加并使用新的代理類,首先將新增代理類作為抽象主題角色的子類,實作在抽象主題中宣告的方法,
模式優缺點
代理模式優點如下:
- 代理模式能協調呼叫者和被呼叫者,在一定程度上降低了系統的耦合度
- 遠程代理使客戶端能訪問在遠程機器上的物件
- 虛擬代理通過使用一個小物件來代表一個大物件,可以減少系統資源的消耗,對系統進行優化并提高速度
- 保護代理可以控制對真實物件的使用權限
代理模式缺點如下:
- 由于在客戶端和真實主題之間增加了代理物件,因此可能會造成請求的處理速度變慢,
- 有些代理模式的實作十分復雜,
模式使用環境
根據代理模式的使用目的,常見的代理模式有以下幾個型別,
- 遠程(Remote)代理:為一個位于不同的地址空間的物件提供一個本地的代理物件,這個不同的地址空間可以在同一臺主機,也可以是另一臺主機,
- 虛擬(Virtual)代理:如果需要創建一個資源消耗較大的物件,可以先創建一個消耗較小的物件來表示,真實物件只在需要時才會被真正創建,
- Copy-on-Write代理:它是虛擬代理的一種,把復雜操作延遲到只在客戶端真正需要時才執行,
- 保護(Protect or Access)代理:控制對一個物件的訪問,可以給不同的用戶提供不同級別的使用權限,
- 緩沖(Cache)代理:為某一個目標操作的結果提供臨時的存盤空間,以便多個客戶可以共享這些結果,
- 防火墻(FireWall)代理:保護目標不讓惡意用戶接近
- 同步化(Synchronization)代理:使幾個用戶能同時使用一個物件而沒有沖突
- 智能(Smart Reference)參考:當一個物件被參考時,提供一些額外的操作,如將此物件被呼叫的次數記錄下來
靜態代理
所謂靜態代理,就是由程式員創建或特定工具自動生成源代碼,也就是在編譯時就已經將介面,被代理類,代理類等確定下來,在程式運行之前,代理類的 .class 檔案就已經生成,簡單來說,上述的實體就屬于靜態代理,PermissionProxy 代理類是我們定義好的,在程式運行之前就已經編譯完成,
動態代理
傳統的代理模式中,客戶端通過 ProxySubject 呼叫 RealSubject 類的方法,同時還在代理類中封裝了其他方法,可以處理一些其他問題,如果按照這種方法使用代理模式,那么真實主題角色必須是是事先已經存在的,并將其作為代理物件的內部成員屬性,如果一個真實主題角色必須對應一個代理主題角色,這將導致系統中的類的個數急劇增加,因此需要想辦法減少系統中類的個數,
Java 自帶的基于介面的動態代理(即只能實作介面的代理)能在運行時根據我們在 Java 代碼中的指示動態生成的代理類,其實作相關類位于 java.lang.reflect 包,運行時動態地對某些東西作代理,主要涉及兩個類
-
InvocationHandler 介面
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler.When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.InvocationHandler 是由代理實體的呼叫處理程式實作的介面,每個代理實體都有一個關聯的呼叫處理程式,InvocationHandler 介面中定義了 invoke 方法,當代理實體呼叫某個方法時,該方法的呼叫將被編碼并調度到其呼叫處理程式的 invoke 方法處理,
/** * 處理代理實體上的方法呼叫并回傳結果 * proxy 表示動態代理類 * method 表示需要代理的方法 * args 表示代理方法的引數陣列 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; -
Proxy 類
/** * 該類提供了用于為介面創建代理實體的靜態方法 */ public class Proxy implements java.io.Serializable { ... /** * 根據傳入的介面型別回傳一個動態創建的代理類實體 * loader 表示被代理類的類加載器 * interfaces 表示被代理類實作的介面串列(與真實主題類的介面串列一致) * h 表示所指派的呼叫處理程式類 */ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { ... }下面通過一個簡單實體來學習動態代理,現在有兩個真實主題類分別是 RealSubjectA 和 RealSubjectB,它們對于抽象主題類中定義的抽象方法 request() 提供了不同的實作,在不增加新的代理類的情況下,使得客戶端通過一個代理類來動態選擇所代理的真實主題物件
-
抽象主題介面 AbstractSubject
public interface AbstractSubject { public void request(); } -
真實主題類一 RealSubjectA
public class RealSubjectA implements AbstractSubject { @Override public void request() { System.out.println("真實主題類A"); } } -
真實主題類二 RealSubjectB
public class RealSubjectB implements AbstractSubject { @Override public void request() { System.out.println("真實主題類B"); } } -
動態代理類 DynamicProxy
public class DynamicProxy implements InvocationHandler { private Object obj; public DynamicProxy() {} public DynamicProxy(Object obj) { this.obj = obj; } // 實作 invoke() 方法,呼叫在真實主題類中定義的方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("呼叫之前"); // 利用反射呼叫方法,如果方法沒有回傳值則為 null Object result = method.invoke(obj, args); System.out.println("呼叫之后"); return result; } } -
客戶端測驗類 Client
public class Client { public static void main(String[] args) { AbstractSubject subject = new RealSubjectA(); InvocationHandler handler = new DynamicProxy(subject); AbstractSubject subjectProxy = (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler); subjectProxy.request(); System.out.println("-----------------------------"); subject = new RealSubjectB(); handler = new DynamicProxy(subject); subjectProxy = (AbstractSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler); subjectProxy.request(); } } -
運行結果

-
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/43461.html
標籤:其他
上一篇:分享阿里的技術
