Spring/Boot/Cloud系列知識(2)— — 代理模式

本文轉自:https://blog.csdn.net/yinwenjie/article/details/77848285

代理模式是23種設計模式中的一種,屬於一種結構模式。用一句大白話解釋這個設計模式:當外部調用者調用A來完成一件事情/一個動作時,並不直接調用A本身,而是調用一個代理者,並再由代理者負責調用真實的執行者A,最終達到間接調用的目的。

代理模式(動態)和Cglib代理是Spring生態中的最基礎設計原理之一,所以要了解Spring的工作原理就必須先討論清楚代理模式的設計思路(無論是靜態代理還是動態代理)。

1. 代理模式(靜態)
 


(代理模式——靜態)

上圖顯示了一個標準的代理模式(靜態)類關係結構。在標準的代理模式(靜態)中,至少存在以下角色:

  • Subject接口或抽象角色:對於外部調用者來說只關心調用操作是否被執行,而不會關心本次調用是被直接執行的還是被代理者間接執行的。所以一般來說代理執行者會實現和被代理者相同的接口(或抽象類),“僞裝”成被代理的對象(上圖中就是僞裝成RealSubject類)。
  • RealSubject:被代理的真實業務執行者。簡單來說就是真實業務由這個角色負責執行,只是爲了在業務處理前後能夠執行其它操作,所以真實業務執行者纔會被代理(上圖中就是被Proxy代理)。
  • Proxy:代理執行者。代理執行者內部引用了真實執行者,並根據需要在真實業務執行前後,執行其它操作:例如判斷入參是否符合要求、打開數據庫連接、捕獲異常發送到事件蒐集器中……

以下的代碼片段說明了代理模式(靜態)中以上幾個工作角色的簡單實現:

  • Subject接口或抽象角色
// 業務接口定義
public interface Subject {
  // 該方法是被外部調用者所調用的方法
  public void operation();
}
  • Proxy 代理者
/**
 * 代理者,代理者並不執行最終的業務<br>
 * 但是可以在執行最終業務之前、之後做一些其它處理。例如打開數據庫連接、提交事務等等
 * @author yinwenjie
 */
public class Proxy implements Subject{
  // 真實執行者
  private RealSubject realSubject;
  // 建議使用slf4j
  private static final Logger LOG = LoggerFactory.getLogger(Proxy.class);

  public Proxy() {
    this.realSubject = new RealSubject();
  }
  public void operation() {
    try {
      LOG.info("在執行真實調用前,可以執行一些動作");
      this.realSubject.operation();
      LOG.info("在執行真實調用後,也可以執行一些動作");
    } catch (Exception e) {
      LOG.info("在執行真實調用異常後,還是可以執行一些動作");
    }
  }
}
  • RealSubject,真實業務在這裏執行
// 這是真正的業務執行者
public class RealSubject implements Subject {
  // 建議使用slf4j
  private static final Logger LOG = LoggerFactory.getLogger(RealSubject.class);

  public void operation() {
    this.exec();
  }
  // 真實業務在這個私有方法中執行
  private void exec() {
    LOG.info("執行真實的業務!");
    // 有10%的機率拋出異常
    float currentValue = new Random().nextFloat();
    if(currentValue < 0.01) {
      throw new IllegalArgumentException("拋出了異常");
    }
  }
}

 

  • 以下代碼可以執行這個代理模式設計,並且輸出類似如下的結果:
......
public static void main(String[] args) throws Exception {
  Subject subject = new Proxy();
  subject.operation();
}
......

// 以下是可能的輸出結果================================
54273 [main] INFO yinwenjie.test.proxy.Proxy  - 在執行真實調用前,可以執行一些動作
54273 [main] INFO yinwenjie.test.proxy.RealSubject  - 執行真實的業務!
54480 [main] INFO yinwenjie.test.proxy.Proxy  - 在執行真實調用異常後,還是可以執行一些動作


2. 代理模式(動態)

靜態代理模式很簡單吧。實際工作中,靜態代理模式只適合寫寫測試讓大家學習和熟悉解決問題的思路,真正有用的還是動態代理模式和Cglib代理,例如Spring中就使用了這兩種動態代理方式。本節我們先介紹java.lang.reflect.Proxy代理。

動態代理模式並不要求對某個業務接口(interface)有任何實現,而是一旦有調用發生,就會通知java.lang.reflect.InvocationHandler接口下的invoke方法。

2.1、基本代碼過程

  • 以下是兩個接口定義 TargetOneInterface 和 TargetTwoInterface
// 被代理的接口
public interface TargetOneInterface {
  // 這是一個方法
  public void doSomething();
  // 這是第二個方法
  public void handleSomething();
}
// 第二個需要被代理的接口
public interface TargetTwoInterface {
  // 查詢某些數據
  public List<?> findSomething();
  // 按照某種條件進行查詢
  public List<?> findSomethingByField(Integer field);
}

 

  • 接着我們定義代理處理器

java中對動態代理的支持是通過java.lang.reflect.InvocationHandler接口來規範的。以下是一個實現示例:

// 代理者處理器
public class ProxyInvocationHandler implements InvocationHandler {
  /**
   * @param proxy 代理對象,注意是代理者,不是被代理者
   * @param method 被代理的方法
   * @param args 被執行的代理方法中傳入的參數
   */
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("====================================================");
    System.out.println("代理者的對象:" + proxy.getClass().getName());
    // 被代理的接口
    Class<?> targetClass = method.getDeclaringClass();
    System.out.println("被代理的接口類:" + targetClass.getName());
    System.out.println("被代理的方法:" + method.getName());
    if(args == null) {
      return null;
    }

    System.out.println("被代理的調用過程參數類型:");
    Arrays.asList(args).stream().forEach((item) -> {
      System.out.println("方法類型:" + item.getClass().getName());
    });

    // 在這裏可以返回調用結果 
    return null;
  }
}
  • 接着我們就可以定義如何代理接口了
public class Run {
  public static void main(String[] args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    //這是代理處理器
    ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
    Object proxy = Proxy.newProxyInstance(classLoader,new Class<?>[]{TargetOneInterface.class , TargetTwoInterface.class}, invocationHandler);

    // 開始調用(測試第一個接口)
    TargetOneInterface targetOne = (TargetOneInterface)proxy;
    targetOne.doSomething();
    targetOne.handleSomething();

    // 開始調用(測試第二個接口)
    TargetTwoInterface targetTwo = (TargetTwoInterface)proxy;
    targetTwo.findSomething();
    targetTwo.findSomethingByField(111);
  }
}


請注意,TargetOneInterface接口和TargetTwoInterface接口可沒有定義任何實現。在調用接口中定義的方法時,應用程序也沒有執行接口的任何實現,而是調用了InvocationHandler代理處理器中的invoke()方法。這個方法中有三個參數,在以上代碼的註釋中已經詳細說明,這裏就不在贅訴了。

  • 以下是執行的結果:
====================================================
代理者的對象:com.sun.proxy.$Proxy0
被代理的接口類:yinwenjie.test.proxy.dproxy.target.TargetOneInterface
被代理的方法:doSomething
====================================================
代理者的對象:com.sun.proxy.$Proxy0
被代理的接口類:yinwenjie.test.proxy.dproxy.target.TargetOneInterface
被代理的方法:handleSomething
====================================================
代理者的對象:com.sun.proxy.$Proxy0
被代理的接口類:yinwenjie.test.proxy.dproxy.target.TargetTwoInterface
被代理的方法:findSomething
====================================================
代理者的對象:com.sun.proxy.$Proxy0
被代理的接口類:yinwenjie.test.proxy.dproxy.target.TargetTwoInterface
被代理的方法:findSomethingByField
被代理的調用過程參數類型:
方法類型:java.lang.Integer

<==== 上一篇  =====>

Spring/Boot/Cloud系列知識(1)— — 開篇

<==== 下一篇  =====>

Spring/Boot/Cloud系列知識(3)— — 代理模式(中)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章