1. 程式人生 > >JAVA設計模式 5【結構型】代理模式的理解與使用

JAVA設計模式 5【結構型】代理模式的理解與使用

今天要開始我們`結構型` 設計模式的學習,設計模式源於生活,還是希望能通過生活中的一些小栗子去理解學習它,而不是為了學習而學習這些東西。 ### 結構型設計模式 結構型設計模式又分為 - 類 結構型 - 物件 結構型 > 前者使用物件的`繼承機制`來組織物件和類 後者採用`組合聚合` 的方式來組合物件。 ## 代理模式 Proxy 理解`代理一詞` 代理表達的是:為某個物件提供一種代理,用於控制該物件的訪問,讓客戶端間接的訪問該物件,從而限制、或者增強源物件的一些特性。 ### 舉個栗子 ![image.png](https://file.chaobei.xyz/blogs/image_1596265616646.png_imagess) 從國內`科學SW`,訪問谷歌查閱一些資料的時候,我們肯定務必會藉助一些`代理器` 也就是通常所說的`VPN`,代理的伺服器可以幫助我們完成這些操作。 ### 靜態代理 #### 畫個圖理解一下 ![image.png](https://file.chaobei.xyz/blogs/image_1596265648362.png_imagess) 需要說明的地方有: - 抽象父類或者介面:定義了這個代理可以代理的方法。比如定義了一個`SearchSubject` 實現它的子類必須要實現對應的`search()` 方法。 ```java /** * 抽象主題,可以進行搜尋 */ public abstract class SearchSubject { /** * 可以進行搜尋的操作 */ public abstract void search(); } ``` - 真實物件:真實物件也就是具體將要`被代理的方法`,這個真實物件的方法我們要通過代理類`間接的去訪問`。 > 眾所周知,國內訪問不到Google,需要代理才行。 ```java public class Google extends SearchSubject { @Override public void search() { System.out.println("Google 搜尋引擎"); } } ``` - 代理類:也就是VPN ,幫助我們訪問`真實物件` 的某些方法,並且還可以做一些增強。比如在訪問`真實物件之前`做一些事情,之後做一些事情。 ```java /** * VPN 代理 * 靜態代理也需要實現抽象主題 */ public class VPNProxy extends SearchSubject { /** * 含有真實主題 */ private Google google; @Override public void search() { if (null == google) { google = new Google(); } this.before(); /** * 呼叫真實物件的方法 */ google.search(); this.after(); } /** * 增強方法 */ public void before() { System.out.println("VPN 開始執行。。。"); } public void after() { System.out.println("VPN 結束執行"); } } ``` #### 執行呼叫代理 ```java VPNProxy proxy = new VPNProxy(); proxy.search(); ------------------ VPN 開始執行。。。 Google 搜尋引擎 VPN 結束執行 ``` 以上就是我們要學習的第一種代理方式:**靜態代理** ### 動態代理 假設我們還需要代理一個物件呢?比如`必應` 假設`必應搜尋`我們國內訪問不到,必須使用代理的話,是不是又得重新建立兩個物件 - 真實物件`必應搜尋` - 代理物件`必應搜尋的代理` 這就不利於我們系統的擴充套件性,假設有很多需要代理的,那豈不是寫一大堆。 > 因此,動態代理由此而生。 這裡我們使用JDK 提供的動態代理 ```java public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h){} ``` - ClassLoader 類載入器 - interfaces 載入的介面 - InvocationHandler 增強處理器以及呼叫代理的類 #### 建立一個可供實現的搜尋介面 ```java /** * 搜尋介面 */ public interface SearchInterface { String search(); } ``` 谷歌搜尋引擎實現了這個介面,並且將名稱作為返回值返回。 ```java public class GoogleSearch implements SearchInterface { @Override public String search() { System.out.println("Google 搜尋引擎"); return "Google"; } } ``` 建立一個搜尋增強器,並且建立了兩個方法的增強,在呼叫代理之前和之後,都加入了一些方法。 ```java /** * 搜尋處理器 */ public class SearchHandler implements InvocationHandler { private void before() { System.out.println("handler start"); } private void after() { System.out.println("handler stop"); } private SearchInterface obj; public SearchHandler(SearchInterface obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { this.before(); /** * 執行代理方法 */ Object result = method.invoke(obj, args); System.out.println("result=" + result.toString()); this.after(); return result; } } ``` 建立一個動態代理工廠,將我們需要代理的介面傳入,並且傳入介面的處理類,即可實現介面的增強處理。 ```java /** * 動態代理工廠 */ public class ProxyFactory { /** * 目標物件 */ private SearchInterface object; private InvocationHandler handler; public ProxyFactory(SearchInterface obj, InvocationHandler handler) { this.object = obj; this.handler = handler; } /** * 獲取代理物件 * @return */ public Object getProxyObj() { ClassLoader classLoader = object.getClass().getClassLoader(); Class[] interfaces = object.getClass().getInterfaces(); return Proxy.newProxyInstance(classLoader, interfaces, handler); } } ``` 建立一個具體的介面物件,傳入我們的代理工廠,並且將其處理器也同時傳入,我們就可以得到一個代理物件。 ```java SearchInterface search = new GoogleSearch(); System.out.println("1id=" + search); InvocationHandler handler = new SearchHandler(search); ProxyFactory factory = new ProxyFactory(search, handler); SearchInterface google = (SearchInterface) factory.getProxyObj(); System.out.println("2id=" + google); google.search(); ----------------- 1id=impl.GoogleSearch@1b6d3586 handler start result=impl.GoogleSearch@1b6d3586 handler stop 2id=impl.GoogleSearch@1b6d3586 handler start Google 搜尋引擎 result=Google handler stop ``` 從上面的程式碼我們發現: - 代理的物件與我們建立的物件有所不同 - 在生成代理物件的時候、已經執行了一遍invoke() 方法 - 通過代理物件呼叫具體方法的時候也執行了一遍invoke() #### 老衲畫個圖 ![image.png](https://file.chaobei.xyz/blogs/image_1596265677118.png_imagess) 這樣就好理解多了,代理工廠需要一個代理類、以及這個代理類的增強方法(處理器),通過代理工廠生成的代理物件,實現對物件的增強處理。 #### 動態代理的總結 - 代理類不需要實現介面,但是具體物件還是需要實現介面。 ### Cglib代理 上面兩種代理,都是需要代理類、或者是具體的目標物件實現某個介面的基礎上出現的,假設沒有這個介面的顯示,我只想在某個具體的物件上加入增強的話,如何實現呢? Cglib代理又被稱作`子類代理`,就是代理一個具體的子類 > 因為Spring 已經引入了相關的Cglib 的依賴,我們直接在Spring 的環境下進行測試。 建立一個具體的子類。沒有實現任何的介面 ```java public class BingSearch { public void search() { System.out.println("必應搜尋。。。"); } } ``` 建立類實現一個方法攔截器,其實名字就是這樣叫的。我們的代理物件,是通過工具類拿出來的。 ```java public class ProxyFactory implements MethodInterceptor { //維護目標物件 private Object target; public ProxyFactory(Object target) { this.target = target; } private void before() { System.out.println("代理類前置處理。。"); } private void after() { System.out.println("代理類後置處理。。"); } public Object getProxy() { //1.工具類 Enhancer en = new Enhancer(); //2.設定父類 en.setSuperclass(target.getClass()); //3.設定回撥函式 en.setCallback(this); //4.建立子類(代理物件) return en.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { /** * 執行方法 */ this.before(); Object result = method.invoke(target, objects); this.after(); return result; } } ``` 在main 方法對一個具體的類進行增強代理。 ```java public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(new BingSearch()); BingSearch bing = (BingSearch) proxyFactory.getProxy(); bing.search(); } --------- 代理類前置處理。。 必應搜尋。。。 代理類後置處理。。 ``` ## 小結 本節,將我們最常用的兩種代理模式進行了一些講解,其實最重要的是`JDK動態代理` 和`Cglib 具體方法代理增強`。因為大家已經擁抱Spring 的懷抱了,這兩種代理還是很重要的,Spring的AOP 切面也是一種基於動態代理的方式實現。非常好用,在Spring 宣告式事務當中,一個註解即可搞定許多冗餘的程式設計式事務,這無不歸功於 強大的`動態代理` ### 鳴謝&參考 https://www.cnblogs.com/leeego-123/p/10995975.html ### 程式碼 https://gitee.com/mrc1999/Dev-Examples ### 歡迎關注 ![banner](https://file.chaobei.xyz/blogs/banner_1591192617