當直接訪問某些對象存在問題時,能夠經過一個代理對象來間接訪問,爲了保證客戶端使用的透明性,所訪問的真實對象與代理對象須要實現相同的接口。java
某些狀況下,一個客戶不想或不能直接引用一個對象,此時能夠經過一個稱之爲代理的第三者實現間接引用。代理對象在客戶端和目標對象之間起到中介做用,而且能夠經過代理對象去掉客戶不能看到的內容和添加客戶須要的額外服務。程序員
給某一個對象提供一個代理,並由代理對象控制對原對象的引用。代理模式的英文叫作 Proxy 或 Surrogate,它是一種對象結構模式。編程
Subject(抽象主題角色)數組
聲明瞭真實主題和代理主題的公共接口,這樣一來在任何使用真實主題的地方均可以使用代理主題。客戶端針對抽象主題角色編程。ide
Proxy(代理主題角色)工具
代理主題角色內部包含對真實主題的引用,從而能夠在任什麼時候候操做真實主題角色。學習
代理主題角色中提供一個與真實主題角色相同的接口,以便在任什麼時候候替代真實主體。測試
代理主題角色還能夠控制對真實主題的使用,負責在須要時建立和刪除真實主題對象,並對真實主題對象的使用加以約束。優化
代理角色一般在客戶端調用所引用的真實主題操做以前或以後執行其餘操做,而不只僅只是單純調用真實主題對象中的操做。this
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(); } }
運行結果
代理類能夠對用戶訪問權限進行控制,所以有些用戶無權調用真實業務類的某些方法,當用戶權限改變時,則能夠訪問這些方法。若是須要增長並使用新的代理類,首先將新增代理類做爲抽象主題角色的子類,實如今抽象主題中聲明的方法。
代理模式優勢以下:
代理模式缺點以下:
根據代理模式的使用目的,常見的代理模式有如下幾個類型。
所謂靜態代理,就是由程序員建立或特定工具自動生成源代碼,也就是在編譯時就已經將接口,被代理類,代理類等肯定下來。在程序運行以前,代理類的 .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(); } }
運行結果