代理模式 PROXY Surrogate 結構型 設計模式(十四)
阿新 • • 發佈:2018-12-05
代理模式 PROXY 別名Surrogate
聲明瞭真實主題和代理主題的共同介面,任何使用真實主題的地方,都可以使用代理主題
代理主題角色ProxySubject
代理主題角色內部含有對真實物件的引用,從而可以在任何時候操作真實主題
代理主題提供與真實主題的相同的介面,以便任何時刻,都可以替代真實主題
而且,代理主題還可以在真實主題執行前後增加額外的處理,比如:經紀人要先收下費~
真實主題角色RealSubject
被代理的真實主題物件,真正工作的是他,比如經紀人總不會站在舞臺上去~
為一個位於不同的地址空間的物件提供一個局域代表物件,這個不同的地址空間可以是本機器的,也可以是另一臺機器的 虛擬代理 Virtual 根據需要建立一個資源消耗較大的物件,使得此物件只在需要時才會被真正建立 保護代理 Protect or Access 控制對一個物件的訪問,如果需要,可以給不同的使用者提供不同級別的使用許可權 Cache代理 為一個目標操作的結果提供臨時的儲存空間,以便多個客戶端可以共享這些結果 防火牆代理 Firewall 保護目標,防止惡意行為 同步代理 Synchronization
使幾個使用者能夠同時使用一個物件而沒有衝突
智慧引用 Smart Reference 當一個物件被引用時,提供一些額外的操作,比如將物件呼叫次數記錄下來 很顯然,這些分類其實只是代理的不同應用場景,以後可能還會有更多的分類出來 但是永遠也脫離不了代理的“隔離”“間接”的根本核心。
意圖
為其他的物件提供一種代理以控制對這個物件的訪問。 代理模式含義比較清晰,就是中間人,中介公司,經紀人... 在計算機程式中,代理就表示一個客戶端不想或者不能夠直接引用一個物件 而代理物件可以在客戶端和目標物件之間起到中介的作用結構
代理模式的根本在於隔離,如下圖所示,間接訪問 代理物件如何能夠真的代理真實物件? 在Java語言中,看起來像的一個方式就是實現同一介面 代理角色和真實物件角色擁有共同的抽象型別,他們擁有相同的對外介面request()方法 ProxySubject內部擁有一個RealSubject 你應該能感覺到組合模式的思想-----他們都是Subject,屬於同一個Component 對外有一致的介面 抽象主題角色Subject示例程式碼
Subject 抽象角色 定義了真正的處理請求 的request()方法packageRealSubject真實主題角色,實現了處理請求的方法proxy; public interface Subject { void request(); }
package proxy; public class RealSubject implements Subject { @Override public void request() { System.out.println("realSubject process request...."); } }Proxy代理角色 實現了request()方法,用於替代真實主題,內部呼叫真實主題完成請求 並且額外的提供了pre和after操作
package測試類proxy; public class Proxy implements Subject{ private Subject realSubject; @Override public void request() { preRequest(); realSubject.request(); afterRequest(); } public Proxy(Subject realSubject){ this.realSubject = realSubject; } public void preRequest(){ System.out.println("pre request do sth...."); } public void afterRequest(){ System.out.println("after request do sth...."); } }
package proxy; public class Test { /**請求subject執行請求 * @param subject */ public static void askForSth(Subject subject){ subject.request(); System.out.println("################"); } public static void main(String[] args){ Subject real = new RealSubject(); Subject proxy = new Proxy(real); askForSth(proxy); askForSth(real); } }定義了真實物件,也定義了一個代理物件 檢視他們分別處理請求的結果 從下面的時序圖中,能更好的感受到“間接”的感覺 在真正呼叫真實物件方法前,需要先執行preRequest方法 真實物件方法呼叫後,在執行afterRequest方法
代理實現
代理的實現分類有兩種,靜態代理和動態代理 前面形式描述的代理,就是靜態代理 在編譯時期,就已經編寫生成好了代理類的原始碼,程式執行之前class檔案就已經生成了 這種按照我們上面模式編寫了代理類和真實類的形式就是 靜態代理 靜態代理經常被用來對原有邏輯程式碼進行擴充套件,原有的邏輯不需要變更,但是可以增加更多的處理邏輯 但是,但是如果有很多的物件需要被代理怎麼辦? 如果按照靜態代理的形式,那麼將會出現很多的代理類,勢必導致程式碼的臃腫。 所以後來出現了動態代理JDK代理機制
所謂動態代理,按照字面意思就是動態的進行代理, 動態相對於靜態的含義是不需要事先主動的建立代理類, 可以在執行時需要的時候,動態的建立一個代理類。 動態代理的動態關鍵在於代理類的動態生成,不需要我們實現建立,從class檔案的角度來看的話,是與靜態代理一樣的,仍舊有一個代理類的Class檔案 在Java中提供了內建的動態代理的支援。 Java在 java.lang.reflect包中提供了三個核心 Proxy, InvocationHandler, Method 可以用於動態代理的使用 Java動態代理簡單示例package proxy.MyDynamicProxy; public interface Subject { void doSth(); }
package proxy.MyDynamicProxy; public class RealSubject implements Subject { @Override public void doSth() { System.out.println("real Object do something..."); } }
package proxy.MyDynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class DynamicProxyHandler implements InvocationHandler { private Object realSubject; public DynamicProxyHandler(Object realSubject) { this.realSubject = realSubject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy do something...."); return method.invoke(realSubject, args); } }
package proxy.MyDynamicProxy; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args){ RealSubject realSubject = new RealSubject(); Subject proxy = (Subject) Proxy .newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class}, new DynamicProxyHandler(realSubject)); proxy.doSth(); } }測試結果為: 動態代理到底都做了什麼? 對於靜態代理,我們有一個RealSubject,以及他的超介面Subject Subject定義了方法,RealSubject實現了方法。 然後我們建立了代理類,這個代理類實現了Subject介面,並且將新增的邏輯新增進來,然後通過代理類進行方法呼叫。 在上面的例子中, RealSubject,以及他的超介面Subject含義不變,與靜態代理中的邏輯一樣。 然後我們 建立了一個呼叫處理器DynamicProxyHandler 實現了 InvocationHandler介面 該介面只有一個方法 invoke public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 他有三個引數 proxy - 在其上呼叫方法的代理例項 method - 對應於在代理例項上呼叫的介面方法的 Method 例項。Method 物件的宣告類將是在其中宣告方法的介面,該介面可以是代理類賴以繼承方法的代理介面的超介面。 args - 包含傳入代理例項上方法呼叫的引數值的物件陣列,如果介面方法不使用引數,則為 null。基本型別的引數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的例項中。 最後通過Java提供的代理機制建立了一個代理 Subject proxy = (Subject) Proxy .newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class}, new DynamicProxyHandler(realSubject)); 核心就是 newProxyInstance方法,他建立了一個實現了Subject介面的代理類 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 這個方法也有三個引數 loader - 定義代理類的類載入器 interfaces - 代理類要實現的介面列表 h - 指派方法呼叫的呼叫處理程式 為什麼需要這三個引數呢? 首先,Proxy.newProxyInstance幫你動態的建立方法,肯定要有一個類載入器,上面的示例中我們直接使用的測試類的類載入,這個一般是應用程式 類載入器 再者,動態代理與靜態代理一樣,需要實現同樣的介面,那你實現了哪些介面呢?所以你得把介面列表告訴我 最後,你希望有哪些處理呢?你要把處理器給我 proxy.doSth();執行時,會將當前代理例項,以及當前方法,以及當前方法的引數傳遞給invoke方法,所以就完成代理的功能。 再來重頭理一下:
- 如同靜態代理,需要被代理的物件RealSubject,以及他的超介面Subject
- 需要實現InvocationHandler介面建立一個處理器,新增加的方法邏輯封裝在invoke方法中
- Proxy.newProxyInstance建立代理例項
- 使用建立的代理例項執行方法
CGLIB
還有另外一種形式的動態代理CGLIB 需要兩個Jarpackage proxy.cglib; public class RealSubject{ public void doSth() { System.out.println("realSubject process request...."); } }
package proxy.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class MyHandler implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before do something..."); Object object = methodProxy.invokeSuper(o,objects); System.out.println("after do something..."); return object; } }
package proxy.cglib; import net.sf.cglib.proxy.Enhancer; public class Test { public static void main(String[] args){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(RealSubject.class); enhancer.setCallback(new MyHandler()); RealSubject subject = (RealSubject)enhancer.create(); subject.doSth(); } }在這個示例中,不再需要介面,僅僅只有一個真是物件RealSubject 實現了一個處理器 MyHandler 繼承自 MethodInterceptor,實現了intercept方法 在測試客戶端中,通過四個步驟建立了代理物件,然後藉助於代理物件執行 從 enhancer.setSuperclass(RealSubject.class);這一句或許猜得到,CGLIB不依賴於介面,而是代理類繼承了真實主題類 流程 真實主題物件RealSubject是必不可少的,否則代理模式就沒有意義了 類似JDK的代理模式,處理器也是解耦的,在CGLIB中藉助於MethodInterceptor介面約定,這一步要做的事情的本質與InvocationHandler並沒有什麼太多不同---封裝附加的處理邏輯 藉助於Enhancer用來組裝處理建立邏輯,並且建立代理類 setSuperclass設定需要繼承的類(也就是被代理的類) setCallback設定回撥函式 create建立真正的代理物件。 CGLIB採用繼承的機制,如果一個類是final的怎麼辦?那就歇菜了
JDK代理機制與CGLIB對比
目前到JDK8 據說效能已經優於CGLIB了 JDK機制不需要第三方Jar,JDK預設整合,CGLIB需要引入第三方Jar包 JDK需要依賴真實主題物件實現介面,CGLIB則不需要,CGLIB繼承了真實主題 CGLIB雖然不依賴真實主題實現介面,但是被代理的類不能為final,那樣的類是無法繼承的 通常的做法是如果實現了介面,那麼使用JDK機制,如果沒有實現介面,使用CGLIB代理用途分類
代理模式的根本在於隔離,“間接”,只要隔離,間接,那麼就可以隱藏真實物件,並且增加額外的服務,優化,管理等 比如 隱藏了真實的物件,比如你通過中介租房子,可能到期也沒見過房東 提供了代理層,可以 提供更多服務 比如買賣房屋通過中介可以節省你合同的審校工作,很多人不懂合同中暗藏的貓膩 隱藏真實物件,自然能夠起到一定的 保護作用,避免了直接接觸 比如去學校見孩子,需要先經過老師同意 通過代理,也相當於有一個管家,可以 管理外界對真實物件的接觸訪問 比如,真實物件是電腦,管家類軟體相當於代理,可以限制小孩子對電腦的使用時長 圍繞著代理帶來的特點“隱藏真實物件,並且增加額外的服務,優化,限制” 在多種場景下,延伸出來一些分類 遠端代理 Remote為一個位於不同的地址空間的物件提供一個局域代表物件,這個不同的地址空間可以是本機器的,也可以是另一臺機器的 虛擬代理 Virtual 根據需要建立一個資源消耗較大的物件,使得此物件只在需要時才會被真正建立 保護代理 Protect or Access 控制對一個物件的訪問,如果需要,可以給不同的使用者提供不同級別的使用許可權 Cache代理 為一個目標操作的結果提供臨時的儲存空間,以便多個客戶端可以共享這些結果 防火牆代理 Firewall 保護目標,防止惡意行為 同步代理 Synchronization
使幾個使用者能夠同時使用一個物件而沒有衝突
智慧引用 Smart Reference 當一個物件被引用時,提供一些額外的操作,比如將物件呼叫次數記錄下來 很顯然,這些分類其實只是代理的不同應用場景,以後可能還會有更多的分類出來 但是永遠也脫離不了代理的“隔離”“間接”的根本核心。
總結
代理角色雖然是真實角色的“代理人”,雖然代理角色內部依賴真實角色 但是真實角色可以完全脫離代理人,單獨出現 比如上面示例中的askForSth(proxy);
askForSth(real);
只不過,通過代理角色會有不同的效果 代理人只是會“幫助”解決他能解決的問題,它能提供的服務,他做不了的事情 比如經紀人不會出唱片,對於出唱片的任務還是會委託給真實角色 現實世界中,我們通常說真實角色委託代理角色,比如,房東找中介 在程式世界中,通常卻說代理角色將任務委託給真實角色,委託與被委託都是相對的 要看你到底是站在什麼視角看待問題,無所謂~ 再次強調,代理模式的重點在於增加對真實受訪物件的控制,也可以增加額外的服務。 原文地址: 代理模式 PROXY Surrogate 結構型 設計模式(十四)