面向對象編程內功心法系列十(聊一聊代理模式)

1.引子

代理,咱們都很熟悉,非要舉一個例子的話,好比說生活中的各類中介,好比說明星藝人的經紀人,都是代理,生活中的代理。java

延伸到程序員的世界中也有代理,咱們都很是熟悉代理設計模式。好比說提到spring框架,你必定會想起IOC,和AOP。IOC叫作控制反轉,是工廠設計模式和依賴注入的應用。AOP叫作面向切面編程,相應的應用了代理設計模式。程序員

這麼看來代理兩個字還有許多講究,那麼你有想過代理設計模式,到底解決了什麼問題嗎?或者說日常咱們租房、買房爲何非要找中介呢?spring

在代理這種設計模式中,咱們能夠嘗試問這麼一些問題編程

  • 誰是代理對象?設計模式

  • 誰是被代理(目標對象)?安全

  • 若是沒有代理對象,目標對象會怎麼樣?框架

舉個例子,某歌星須要開一場演唱會,須要作許多準備工做。好比說搭臺、商務簽約、行程安排、最後開唱。咱們常說術業有專攻,對於一個歌手,他擅長於也僅擅長於唱歌,別的不會。jvm

那怎麼辦?請經紀人吶!像商務簽約、行程安排都交給經紀人來作,歌星只須要到點登臺唱歌便可。你看這就是代理,咱們能夠用正式一點的話語來總結描述一下ide

  • 代理是一種機制,一種用於控制訪問目標對象的機制性能

  • 代理,能夠實現對目標對象的保護(要找歌星簽名,先得過經紀人這一關)

  • 代理,能夠實現對目標對象的加強(歌星只須要關心唱歌,別的事情交給經紀人)

到這裏,你應該可以充分理解代理,及代理設計模式了。

那到底什麼是代理設計模式呢?

  • 代理設計模式,它是一種結構型設計模式

  • 有代理對象,被代理對象(目標對象)

  • 經過代理對象,能夠實現對目標對象的訪問控制,增長目標對象安全性

  • 經過代理對象,能夠實如今不改變目標對象的的狀況下,加強目標對象能力

2.案例

在引子部分,咱們搞清楚了什麼是代理設計模式,接下來我將經過案例實現,給你演示一下代理設計模式的應用。

關於代理設計模式,你須要留意它有兩種實現方式,分別是

  • 靜態代理

  • 動態代理

另外在實現的過程當中,一般須要代理對象,與被代理對象實現相同的接口,即它們是同類。咱們具體看代碼案例吧,相信看到代碼你就明白了。

2.1.靜態代理

一般在項目中,咱們將功能分爲

  • 業務功能(好比說用戶、訂單的增刪改查)

  • 非業務功能(好比說事務控制、記錄日誌、性能耗時統計)

對於業務開發工程師來講,只須要關注業務功能的開發。而非業務功能,在每一個項目中都差很少,具有必定的通用性,徹底能夠經過代理設計模式來支持實現。

接下里我將給你模擬一個這樣的案例,咱們以保存用戶接口爲例,在保存用戶的同時進行接口耗時統計。

2.1.1.接口

/**
 * 用戶接口
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/7 16:09
 */
public interface UserService {

    /**
     * 保存用戶接口
     * @param name
     */
    void saveUser(String name);
}

2.1.2.被代理類

/**
 * 用戶接口實現類
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/7 16:10
 */
public class UserServiceImpl implements UserService {

    /**
     * 保存用戶接口
     * @param name
     */
    @Override
    public void saveUser(String name) {
        System.out.println("保存用戶:" + name);
    }
}

2.1.3.代理類

/**
 * 用戶接口代理,與目標對象實現相同的接口
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/7 16:12
 */
public class UserProxy implements UserService{

    private UserService target;

    public UserProxy(UserService target){
        this.target = target;
    }

    /**
     * 保存用戶接口(代理)
     * @param name
     */
    @Override
    public void saveUser(String name) {
        // 統計耗時開始(加強的功能)
        System.out.println("統計保存用戶接口耗時.start:" + System.currentTimeMillis());

        // 調用目標對象
        target.saveUser(name);

        // 統計耗時結束(加強的功能)
        System.out.println("統計保存用戶接口耗時.end:" + System.currentTimeMillis());
    }
}

2.1.4.測試類

public static void main(String[] args) {
  // 建立目標對象
  UserService target = new UserServiceImpl();

  // 建立代理對象
  UserService proxy = new UserProxy(target);

  // 經過代理對象,實現保存用戶的時候,統計耗時
  proxy.saveUser("小明");
}

#執行結果
統計保存用戶接口耗時.start:1615105035485
保存用戶:小明
統計保存用戶接口耗時.end:1615105035485

Process finished with exit code 0

2.1.5.總結分析

UserProxy類有兩個特色

  • 實現UserService接口,與目標對象實現相同的接口

  • 持有UserSerivceImpl實例,經過組合實現目標對象的能力加強

  • 你須要關注UserProxy中的saveUser方法,在保存用戶target.saveUser(name)先後,進行耗時統計

最後經過執行結果,咱們看到在不修改UserSerivceImpl類代碼的狀況下,實現了保存用戶的同時,進行接口耗時統計能力的加強。你看這就是代理設計模式,代碼實現比較簡單。

不過你須要注意,在實際項目中,靜態代理使用很是少,緣由是靜態代理方式,每個目標類都須要相應定義一個代理類,致使類的數量會膨脹,另外代碼維護性比較差

那麼針對靜態代理存在的問題,咱們該如何解決呢?答案是動態代理

2.2.動態代理

經過靜態代理示例代碼,咱們比較直觀的看到了代理設計模式的實現。不過咱們說靜態代理存在兩個問題

  • 每個目標類,都須要一個相應的代理類,類的數量膨脹問題

  • 靜態代理,代碼是在編譯時實現,代碼維護性比較差

針對靜態代理的問題,jdk提供了動態代理的語法支持,接下來咱們一塊兒來看動態代理示例代碼,你須要關注兩個類

  • Proxy,建立代理對象入口類

  • InvocationHandler,具體實現代理加強的接口類

2.2.1.經過動態代理,建立代理類

/**
 * 動態代理示例
 * 1.Proxy,建立代理對象入口類
 * 2.InvocationHandler,具體實現代理加強的接口類
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/7 16:33
 */
public class DynamicUserProxy {

    public static void main(String[] args) {
        // 建立目標對象
        UserService target = new UserServiceImpl();

        // 建立代理對象
        UserService proxy = (UserService)Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 統計耗時開始
                        System.out.println("統計保存用戶接口耗時.start:" + System.currentTimeMillis());

                        // 調用目標對象
                        Object result = method.invoke(target, "小明");

                        // 統計耗時結束
                        System.out.println("統計保存用戶接口耗時.end:" + System.currentTimeMillis());

                        return result;
                    }
                }
        );

        // 經過代理對象,實現保存用戶的時候,統計耗時
        proxy.saveUser("小明");
    }
}


#執行結果
統計保存用戶接口耗時.start:1615106267280
保存用戶:小明
統計保存用戶接口耗時.end:1615106267280

Process finished with exit code 0

2.2.2.總結分析

對比動態代理,靜態代理

  • 執行結果是同樣的,都在不修改目標類UserServiceImpl的狀況下,實現了接口耗時統計能力的加強

  • 代碼結構都遵循了三個步驟

    • 建立目標對象

      // 建立目標對象
      UserService target = new UserServiceImpl();

       

    • 建立代理對象

      #靜態代理
      // 建立代理對象
      UserService proxy = new UserProxy(target);
          
      #動態代理
      // 建立代理對象
      UserService proxy = (UserService)Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InvocationHandler() {
              @Override
              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 統計耗時開始
                System.out.println("統計保存用戶接口耗時.start:" +
                                   System.currentTimeMillis());
      
                 // 調用目標對象
                 Object result = method.invoke(target, "小明");
      
                 // 統計耗時結束
                 System.out.println("統計保存用戶接口耗時.end:" + 
                                    System.currentTimeMillis());
      
                 return result;
               }
            }
      );

       

    • 經過代理對象,實現功能加強

    // 經過代理對象,實現保存用戶的時候,統計耗時
    proxy.saveUser("小明");

     

  • 差別最大的地方,是在建立代理對象。動態代理中,咱們經過Proxy的newProxyInstance方法,建立代理對象,它有三個參數

    • 參數1:類加載器,動態代理的本質是運行時實現加強,即在運行的時候jvm動態生成代理類字節碼Class,從而實例化代理對象。所以,須要類加載器

    • 參數2:目標對象實現的接口列表,符合代理設計模式定義,代理類,與目標類實現相同的接口

    • 參數3:InvocationHandler,用於實現加強的接口,關心接口中的invoke方法,該方法中實現代理邏輯

經過上面動態代理,靜態代理的對比,而且我詳細給你解釋了動態代理相關的代碼細節,相信你能夠很好的理解代理設計模式、靜態代理、動態代理了。

關於代理模式,是一個很重要,且用的比較多的設計模式。最後我想拋出一個知識點,基於jdk提供的Proxy動態代理實現,咱們叫作基於接口的動態代理,它的意思是說目標類必需要實現接口。那麼若是目標類在沒有實現任何接口的狀況下,如何實現動態代理呢?答案是cglib,拋磚引玉指望你能夠去了解一下

相關文章
相關標籤/搜索