23天設計模式之代理模式

孤影的部落格發表於2021-06-20

23天設計模式之代理模式

文章簡介

《23天設計模式之代理模式》是在最近學習反射與註解時,在反射中有關Proxy類的知識,也就順帶複習一下代理模式,總結部落格。

代理模式

  • 為其他物件提供一種代理以控制對這個物件的訪問。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。

  • 組成:

    • 抽象角色:通過介面或抽象類宣告真實角色實現的業務方法。

    • 代理角色:實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,並可以附加自己的操作。

    • 真實角色:實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色呼叫。

  • 舉個例子:

    • 比如 “租房子”、“租客”、“中介”就分別代表 “抽象角色”、“真實角色”、“代理角色”。
    • “租客”與“中介”都要實現“租房子”介面。
    • 將“租客”注入到“中介”中,由“中介”呼叫“租客”的實現方法。
  • 代理分為靜態代理和動態代理。

    • 靜態代理:手動生產代理類。
    • 動態代理:自動生成代理類。

靜態代理

直接上程式碼:

  • 抽象角色
// 租房子 - 抽象角色,用介面或抽象類表示,裡面放真實角色要實現的業務方法
public interface Rent {
    void rent();
}
  • 真實角色
// 租客 - 真實角色
public class Tenant implements Rent {

    private String name;

    public Tenant(String name) {
        this.name = name;
    }

    @Override
    public void rent() {
        System.out.println("租客:" + name + " 租到房子了");
    }
}
  • 代理角色
// 靜態代理類 - 代理角色
public class StaticProxy implements Rent{

    // 將被代理角色注入進來
    private Tenant tenant;

    public void setTenant(Tenant tenant) {
        this.tenant = tenant;
    }

    public void beforeRent() {
        System.out.println("租房前,中介帶租客看房");
    }

    public void afterRent() {
        System.out.println("租房後,中介收取中介費和房租");
    }

    @Override
    public void rent() {
        beforeRent();
        tenant.rent(); // 執行真實角色的代理方法,租房前後可以插入其它業務程式碼。
        afterRent();
    }
}
  • 測試
public class Test {

    public static void main(String[] args) {
        Tenant tenant = new Tenant("孤影");
        StaticProxy proxy = new StaticProxy();
        proxy.setTenant(tenant);
        proxy.rent(); // 可以發現,在這裡已經是代理類在執行代理方法, 而不是Tenant類在執行
    }
}

// 輸出
租房前,中介帶租客看房
租客:孤影 租到房子了
租房後,中介收取中介費和房租

動態代理

  • 動態代理分為兩大類:基於介面的動態代理,基於類的動態代理。
    • 基於介面:jdk動態代理。
    • 基於類:cglib。
    • java位元組碼實現:javassist。
  • 這裡我們主要了解jdk動態代理。
  • 面試時問道我們動態代理是怎麼實現的?我們通常只是回答通過反射實現的,但是具體的卻說不出東西來,因此一定了解Proxy類和InvocationHandler介面。

接下來上程式碼:

  • 抽象角色和真實角色與上文相同。
  • 定義一個InvocationHandler,以此來呼叫代理的方法。
// 動態代理類處理程式 - 通過此類獲取代理角色,得到代理類後,使用代理類執行代理方法
public class DynamicProxyHandler implements InvocationHandler {

    // 注入被代理物件
    private Object target;

    private String before;

    private String after;

    public void setTarget(Object target) {
        this.target = target;
    }

    public DynamicProxyHandler(String before, String after) {
        this.before = before;
        this.after = after;
    }

    // 生成代理角色
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    public void beforeInvoke(String s) {
        System.out.println(s);
    }

    public void afterInvoke(String s) {
        System.out.println(s);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeInvoke(before);
        Object result = method.invoke(target, args);// 通過反射執行代理方法,前後可插入其它業務程式碼
        afterInvoke(after);
        return result;
    }
}
  • 測試
public class Test {

    public static void main(String[] args) {
        Tenant tenant = new Tenant("孤影");
        DynamicProxyHandler handler = new DynamicProxyHandler("租房前,中介帶租客看房", "租房後,中介收取中介費和房租");
        handler.setTarget(tenant);
        Object proxy = handler.getProxy();
        if (proxy instanceof Rent) { // 可以看到,生成的代理類仍然是抽象角色的一個例項
            ((Rent) proxy).rent();
        }
    }
}

// 輸出
租房前,中介帶租客看房
租客:孤影 租到房子了
租房後,中介收取中介費和房租
  • 疑惑:

    • 有同學可能會疑惑生成代理角色時使用的this.getClass().getClassLoader(),為什麼不是target.getClass().getClassLoader()

    • 其實二者獲取的類載入器都是同一個AppClassLoader。

    • 通過列印測試確實是同一個類載入器:

      // 生成代理角色
      public Object getProxy() {
          System.out.println(this.getClass().getClassLoader());
          System.out.println(target.getClass().getClassLoader());
          return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
      }
      
      // 輸出
      sun.misc.Launcher$AppClassLoader@18b4aac2
      sun.misc.Launcher$AppClassLoader@18b4aac2
      
    • 關於類載入器要了解更多請閱讀我的另一篇文章 - 簡單談談對GC垃圾回收的通俗理解,關於類載入器和雙親委派機制講的也比較詳細。

動態代理的好處

  • 可以使真實角色的操作更純粹!不用去關注一些公共的業務。
  • 公共業務就交給代理角色,實現了業務的分工。
  • 公共業務發生擴充套件時,方便集中的管理。
  • 一個動態代理類代理的是一個介面,一般就是對應一類業務。
  • 一個動態代理類可以代理多個類,只要是實現了同一個介面即可。

以上

感謝您花時間閱讀我的部落格,以上就是我對代理模式的一些理解,若有不對之處,還望指正,期待與您交流。

本篇博文系原創,僅用於個人學習,轉載請註明出處。

相關文章