1. 程式人生 > >23種設計模式(3)- 代理模式

23種設計模式(3)- 代理模式

什麼是代理模式?

       代理模式的定義:代理模式給某一個物件提供一個代理物件,並由代理物件控制對原物件的引用。通俗的來講代理模式就是我們生活中常見的中介。

舉個例子來說明:假如說我現在想買一輛二手車,雖然我可以自己去找車源,做質量檢測等一系列的車輛過戶流程,但是這確實太浪費我得時間和精力了。我只是想買一輛車而已為什麼我還要額外做這麼多事呢?於是我就通過中介公司來買車,他們來給我找車源,幫我辦理車輛過戶流程,我只是負責選擇自己喜歡的車,然後付錢就可以了。用圖表示如下:

為什麼要用代理模式?

  • 中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委託物件,而代理類物件可以在客戶類和委託物件之間起到中介的作用,其特徵是代理類和委託類實現相同的介面。
  • 開閉原則,增加功能:代理類除了是客戶類和委託類的中介之外,我們還可以通過給代理類增加額外的功能來擴充套件委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合程式碼設計的開閉原則。代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後對返回結果的處理等。代理類本身並不真正實現服務,而是同過呼叫委託類的相關方法,來提供特定的服務。真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公共的服務。例如我們想給專案加入快取、日誌這些功能,我們就可以使用代理類來完成,而沒必要開啟已經封裝好的委託類。

有哪幾種代理模式?

       我們有多種不同的方式來實現代理。如果按照代理建立的時期來進行分類的話, 可以分為兩種:靜態代理、動態代理。靜態代理是由程式設計師建立或特定工具自動生成原始碼,在對其編譯。在程式設計師執行之前,代理類.class檔案就已經被建立了。動態代理是在程式執行時通過反射機制動態建立的。

1.靜態代理     

 一步:建立服務類介面

 1 package main.java.proxy;
 2 
 3 /**
 4  * @Auther: dan gao
 5  * @Description:
 6  * @Date: 22:40 2018/1/9 0009
 7  */
 8 public interface BuyHouse {
 9     void buyHosue();
10 }

 

第二步:實現服務介面

 1 import main.java.proxy.BuyHouse;
 2 
 3 /**
 4  * @Auther: dan gao
 5  * @Description:
 6  * @Date: 22:42 2018/1/9 0009
 7  */
 8 public class BuyHouseImpl implements BuyHouse {
 9 
10     @Override
11     public void buyHosue() {
12         System.out.println("我要買房");
13     }
14 }

 

第三步:建立代理類

 1 package main.java.proxy.impl;
 2 
 3 import main.java.proxy.BuyHouse;
 4 
 5 /**
 6  * @Auther: dan gao
 7  * @Description:
 8  * @Date: 22:43 2018/1/9 0009
 9  */
10 public class BuyHouseProxy implements BuyHouse {
11 
12     private BuyHouse buyHouse;
13 
14     public BuyHouseProxy(final BuyHouse buyHouse) {
15         this.buyHouse = buyHouse;
16     }
17 
18     @Override
19     public void buyHosue() {
20         System.out.println("買房前準備");
21         buyHouse.buyHosue();
22         System.out.println("買房後裝修");
23 
24     }
25 }

 

第四步:編寫測試類

import main.java.proxy.impl.BuyHouseImpl;
import main.java.proxy.impl.BuyHouseProxy;

/**
 * @Auther: dan gao
 * @Description:
 * @Date: 22:43 2018/1/9 0009
 */
public class ProxyTest {
    public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        buyHouse.buyHosue();
        BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
        buyHouseProxy.buyHosue();
    }
}

 

靜態代理總結:

優點:可以做到在符合開閉原則的情況下對目標物件進行功能擴充套件。

缺點:我們得為每一個服務都得建立代理類,工作量太大,不易管理。同時介面一旦發生改變,代理類也得相應修改。                                             

2.動態代理

  在動態代理中我們不再需要再手動的建立代理類,我們只需要編寫一個動態處理器就可以了。真正的代理物件由JDK再執行時為我們動態的來建立。

第一步:編寫動態處理器

 1 package main.java.proxy.impl;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  * @Auther: dan gao
 8  * @Description:
 9  * @Date: 20:34 2018/1/12 0012
10  */
11 public class DynamicProxyHandler implements InvocationHandler {
12 
13     private Object object;
14 
15     public DynamicProxyHandler(final Object object) {
16         this.object = object;
17     }
18 
19     @Override
20     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
21         System.out.println("買房前準備");
22         Object result = method.invoke(object, args);
23         System.out.println("買房後裝修");
24         return result;
25     }
26 }

 

第二步:編寫測試類

 1 package main.java.proxy.test;
 2 
 3 import main.java.proxy.BuyHouse;
 4 import main.java.proxy.impl.BuyHouseImpl;
 5 import main.java.proxy.impl.DynamicProxyHandler;
 6 
 7 import java.lang.reflect.Proxy;
 8 
 9 /**
10  * @Auther: dan gao
11  * @Description:
12  * @Date: 20:38 2018/1/12 0012
13  */
14 public class DynamicProxyTest {
15     public static void main(String[] args) {
16         BuyHouse buyHouse = new BuyHouseImpl();
17         BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
18                 Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
19         proxyBuyHouse.buyHosue();
20     }
21 }

 

 注意Proxy.newProxyInstance()方法接受三個引數:

  • ClassLoader loader:指定當前目標物件使用的類載入器,獲取載入器的方法是固定的
  • Class<?>[] interfaces:指定目標物件實現的介面的型別,使用泛型方式確認型別
  • InvocationHandler:指定動態處理器,執行目標物件的方法時,會觸發事件處理器的方法

動態代理總結:雖然相對於靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務介面的依賴,降低了耦合度。但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支援interface代理的桎梏,因為它的設計註定了這個遺憾。回想一下那些動態生成的代理類的繼承關係圖,它們已經註定有一個共同的父類叫Proxy。Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理,原因是多繼承在Java中本質上就行不通。有很多條理由,人們可以否定對 class代理的必要性,但是同樣有一些理由,相信支援class動態代理會更美好。介面和類的劃分,本就不是很明顯,只是到了Java中才變得如此的細化。如果只從方法的宣告及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因為沒有實現任何介面而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。但是,不完美並不等於不偉大,偉大是一種本質,Java動態代理就是佐例。

3.CGLIB代理

       JDK實現動態代理需要實現類通過介面定義業務方法,對於沒有介面的類,如何實現動態代理呢,這就需要CGLib了。CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。

第一步:建立CGLIB代理類

 1 package dan.proxy.impl;
 2 
 3 import net.sf.cglib.proxy.Enhancer;
 4 import net.sf.cglib.proxy.MethodInterceptor;
 5 import net.sf.cglib.proxy.MethodProxy;
 6 
 7 import java.lang.reflect.Method;
 8 
 9 /**
10  * @Auther: dan gao
11  * @Description:
12  * @Date: 20:38 2018/1/16 0016
13  */
14 public class CglibProxy implements MethodInterceptor {
15     private Object target;
16     public Object getInstance(final Object target) {
17         this.target = target;
18         Enhancer enhancer = new Enhancer();
19         enhancer.setSuperclass(this.target.getClass());
20         enhancer.setCallback(this);
21         return enhancer.create();
22     }
23 
24     public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
25         System.out.println("買房前準備");
26         Object result = methodProxy.invoke(object, args);
27         System.out.println("買房後裝修");
28         return result;
29     }
30 }

 

第二步:建立測試類

 1 package dan.proxy.test;
 2 
 3 import dan.proxy.BuyHouse;
 4 import dan.proxy.impl.BuyHouseImpl;
 5 import dan.proxy.impl.CglibProxy;
 6 
 7 /**
 8  * @Auther: dan gao
 9  * @Description:
10  * @Date: 20:52 2018/1/16 0016
11  */
12 public class CglibProxyTest {
13     public static void main(String[] args){
14         BuyHouse buyHouse = new BuyHouseImpl();
15         CglibProxy cglibProxy = new CglibProxy();
16         BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse);
17         buyHouseCglibProxy.buyHosue();
18     }
19 }

 

CGLIB代理總結: CGLIB建立的動態代理物件比JDK建立的動態代理物件的效能更高,但是CGLIB建立代理物件時所花費的時間卻比JDK多得多。所以對於單例的物件,因為無需頻繁建立物件,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由於CGLib由於是採用動態建立子類的方法,對於final修飾的方法無法進行代理。

文章轉載於 https://www.cnblogs.com/daniels/p/8242592.html