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

程式猿小碼 發表於 2020-08-01

今天要開始我們結構型 設計模式的學習,設計模式源於生活,還是希望能通過生活中的一些小栗子去理解學習它,而不是為了學習而學習這些東西。

結構型設計模式

結構型設計模式又分為

  • 類 結構型
  • 物件 結構型

前者使用物件的繼承機制來組織物件和類
後者採用組合聚合 的方式來組合物件。

代理模式 Proxy

理解代理一詞 代理表達的是:為某個物件提供一種代理,用於控制該物件的訪問,讓客戶端間接的訪問該物件,從而限制、或者增強源物件的一些特性。

舉個例子

image.png

從國內科學SW,訪問谷歌查閱一些資料的時候,我們肯定務必會藉助一些代理器 也就是通常所說的VPN,代理的伺服器可以幫助我們完成這些操作。

靜態代理

畫個圖理解一下

image.png

需要說明的地方有:

  • 抽象父類或者介面:定義了這個代理可以代理的方法。比如定義了一個SearchSubject 實現它的子類必須要實現對應的search() 方法。
/**
 * 抽象主題,可以進行搜尋
 */
public abstract class SearchSubject {
    /**
     * 可以進行搜尋的操作
     */
    public abstract void search();
}
  • 真實物件:真實物件也就是具體將要被代理的方法,這個真實物件的方法我們要通過代理類間接的去訪問

眾所周知,國內訪問不到Google,需要代理才行。

public class Google extends SearchSubject {
    @Override
    public void search() {
        System.out.println("Google 搜尋引擎");
    }
}
  • 代理類:也就是VPN ,幫助我們訪問真實物件 的某些方法,並且還可以做一些增強。比如在訪問真實物件之前做一些事情,之後做一些事情。
/**
 * 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 結束執行");
    }
}

執行呼叫代理

VPNProxy proxy = new VPNProxy();
proxy.search();
------------------
VPN 開始執行。。。
Google 搜尋引擎
VPN 結束執行

以上就是我們要學習的第一種代理方式:靜態代理

動態代理

假設我們還需要代理一個物件呢?比如必應 假設必應搜尋我們國內訪問不到,必須使用代理的話,是不是又得重新建立兩個物件

  • 真實物件必應搜尋
  • 代理物件必應搜尋的代理

這就不利於我們系統的擴充套件性,假設有很多需要代理的,那豈不是寫一大堆。

因此,動態代理由此而生。

這裡我們使用JDK 提供的動態代理

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){}
  • ClassLoader 類載入器
  • interfaces 載入的介面
  • InvocationHandler 增強處理器以及呼叫代理的類

建立一個可供實現的搜尋介面

/**
 * 搜尋介面
 */
public interface SearchInterface {
    String search();
}

谷歌搜尋引擎實現了這個介面,並且將名稱作為返回值返回。

public class GoogleSearch implements SearchInterface {
    @Override
    public String search() {
        System.out.println("Google 搜尋引擎");
        return "Google";
    }
}

建立一個搜尋增強器,並且建立了兩個方法的增強,在呼叫代理之前和之後,都加入了一些方法。

/**
 * 搜尋處理器
 */
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;
    }
}

建立一個動態代理工廠,將我們需要代理的介面傳入,並且傳入介面的處理類,即可實現介面的增強處理。

/**
 * 動態代理工廠
 */
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);
    }
}

建立一個具體的介面物件,傳入我們的代理工廠,並且將其處理器也同時傳入,我們就可以得到一個代理物件。

        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();
-----------------
[email protected]

handler start
[email protected]
handler stop

[email protected]
handler start
Google 搜尋引擎
result=Google
handler stop

從上面的程式碼我們發現:

  • 代理的物件與我們建立的物件有所不同
  • 在生成代理物件的時候、已經執行了一遍invoke() 方法
  • 通過代理物件呼叫具體方法的時候也執行了一遍invoke()

老衲畫個圖

image.png

這樣就好理解多了,代理工廠需要一個代理類、以及這個代理類的增強方法(處理器),通過代理工廠生成的代理物件,實現對物件的增強處理。

動態代理的總結

  • 代理類不需要實現介面,但是具體物件還是需要實現介面。

Cglib代理

上面兩種代理,都是需要代理類、或者是具體的目標物件實現某個介面的基礎上出現的,假設沒有這個介面的顯示,我只想在某個具體的物件上加入增強的話,如何實現呢?

Cglib代理又被稱作子類代理,就是代理一個具體的子類

因為Spring 已經引入了相關的Cglib 的依賴,我們直接在Spring 的環境下進行測試。

建立一個具體的子類。沒有實現任何的介面

public class BingSearch {
    public void search() {
        System.out.println("必應搜尋。。。");
    }
}

建立類實現一個方法攔截器,其實名字就是這樣叫的。我們的代理物件,是通過工具類拿出來的。

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 方法對一個具體的類進行增強代理。

    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