Spring中如何使用責任鏈模式

       關於責任鏈模式,其有兩種形式,一種是經過外部調用的方式對鏈的各個節點調用進行控制,從而進行鏈的各個節點之間的切換;另外一種是鏈的每一個節點自由控制是否繼續往下傳遞鏈的進度,這種比較典型的使用方式就是Netty中的責任鏈模式。本文主要講解咱們如何在Spring中使用這兩種責任鏈模式。前端

1. 外部控制模式

       對於外部控制的方式,這種方式比較簡單,鏈的每一個節點只須要專一於各自的邏輯便可,而當前節點調用完成以後是否繼續調用下一個節點,這個則由外部控制邏輯進行。這裏咱們以一個過濾器的實現邏輯爲例進行講解,在日常工做中,咱們常常須要根據一系列的條件對某個東西進行過濾,好比任務服務的設計,在執行某個任務時,其須要通過諸如時效性檢驗,風控攔截,任務完成次數等過濾條件的檢驗以後才能判斷當前任務是否可以執行,只有在全部的過濾條件都完成以後,咱們才能執行該任務。那麼這裏咱們就能夠抽象出一個Filter接口,其設計以下:java

public interface Filter {

  /**
   * 用於對各個任務節點進行過濾
   */
  boolean filter(Task task);

}

        這裏的Filter.filter()方法只有一個參數Task,主要就是控制當前task是否須要被過濾掉,其有一個boolean類型的返回值,經過該返回值以告知外部控制邏輯是否須要將該task過濾掉。對於該接口的子類,咱們只須要將其聲明爲Spring所管理的一個bean便可:數據庫

// 時效性檢驗
@Component
public class DurationFilter implements Filter {

  @Override
  public boolean filter(Task task) {
    System.out.println("時效性檢驗");
    return true;
  }
}
// 風控攔截
@Component
public class RiskFilter implements Filter {

  @Override
  public boolean filter(Task task) {
    System.out.println("風控攔截");
    return true;
  }
}
// 次數限制校驗
@Component
public class TimesFilter implements Filter {

  @Override
  public boolean filter(Task task) {
    System.out.println("次數限制檢驗");
    return true;
  }
}

       上面咱們模擬聲明瞭三個Filter的子類,用於設計一系列的控制當前task是否須要被過濾的邏輯,結構上的邏輯其實比較簡單,主要就是須要將其聲明爲Spring所管理的一個bean。下面是咱們的控制邏輯:app

@Service
public class ApplicationService {

  @Autowired
  private List<Filter> filters;

  public void mockedClient() {
    Task task = new Task(); // 這裏task通常是經過數據庫查詢獲得的
    for (Filter filter : filters) {
      if (!filter.filter(task)) {
        return;
      }
    }

    // 過濾完成,後續是執行任務的邏輯
  }
}

       在上述的控制邏輯中,對於過濾器的獲取,只須要經過Spring的自動注入便可,這裏注入的是一個List<Filter>,也就是說,若是咱們有新的Filter實例須要參與責任鏈的過濾,只須要將其聲明爲一個Spring容器所管理的bean便可。框架

       這種責任鏈設計方式的優勢在於鏈的控制比較簡單,只須要實現一個統一的接口便可,其基本上可以知足大部分的邏輯控制,可是對於某些須要動態調整鏈的需求其就無能爲力了。好比在執行到某個節點以後須要動態的判斷是否執行下一個節點,或者說要執行某些分叉的節點等等。這個時候咱們就須要將鏈節點的傳遞工做交由各個節點進行。ide

2. 節點控制模式

       對於節點控制調用的方式,其主要有三個控制點:Handler,HandlerContext和Pipeline。Handler中是用於編寫具體的業務代碼的;HandlerContext則主要是用於對Handler進行包裹,而且用於控制進行下一個節點的調用的;Pipeline則主要是用於控制總體的流程調用的,好比對於任務的執行,其有任務的查詢,任務的過濾和執行任務等等流程,這些流程總體的邏輯控制就是由Pipeline來控制的,在每一個流程中又包含了一系列的子流程,這些子流程則是由一個個的HandlerContext和Handler進行梳理的。這種責任鏈的控制方式總體邏輯以下圖所示:函數

責任鏈模式

       從圖中能夠看出,咱們將整個流程經過Pipeline對象進行了抽象,這裏主要分爲了三個步驟:查詢task,過濾task和執行task。在每一個步驟中,咱們都使用了一系列的鏈式調用。圖中須要注意的是,在每次調用鏈的下一個節點的時候,咱們都是經過具體的Handler進行的,也就是說是否進行鏈的下一個節點的調用,咱們是經過業務實現方來進行動態控制的。post

       關於該模式的設計,咱們首先須要強調的就是Handler接口的設計,其設計以下所示:this

public interface Handler {

  /**
   * 處理接收到前端請求的邏輯
   */
  default void receiveTask(HandlerContext ctx, Request request) {
    ctx.fireTaskReceived(request);
  }

  /**
   * 查詢到task以後,進行task過濾的邏輯
   */
  default void filterTask(HandlerContext ctx, Task task) {
    ctx.fireTaskFiltered(task);
  }

  /**
   * task過濾完成以後,處理執行task的邏輯
   */
  default void executeTask(HandlerContext ctx, Task task) {
    ctx.fireTaskExecuted(task);
  }

  /**
   * 當實現的前面的方法拋出異常時,將使用當前方法進行異常處理,這樣能夠將每一個handler的異常
   * 都只在該handler內進行處理,而無需額外進行捕獲
   */
  default void exceptionCaught(HandlerContext ctx, Throwable e) {
    throw new RuntimeException(e);
  }

  /**
   * 在整個流程中,保證最後必定會執行的代碼,主要是用於一些清理工做
   */
  default void afterCompletion(HandlerContext ctx) {
    ctx.fireAfterCompletion(ctx);
  }
}

       這裏的Handler接口主要是對具體的業務邏輯的一個抽象,對於該Handler主要有以下幾點須要說明:prototype

  • 在前面圖中Pipeline的每一個層級中對應於該Handler都有一個方法,在須要進行具體的業務處理的時候,用戶只須要聲明一個bean,具體實現某個當前業務所須要處理的層級的方法便可,而無需管其餘的邏輯;
  • 每一個層級的方法中,第一個參數都是一個HandlerContext類型的,該參數主要是用於進行流程控制的,好比是否須要將當前層級的調用鏈往下繼續傳遞,這裏鏈的傳遞工做主要是經過ctx.fireXXX()方法進行的;
  • 每一個層級的方法都有默認實現,默認實現方式就是將鏈的調用繼續往下進行傳遞;
  • 每一個Handler中都有一個exceptionCaught()方法和afterCompletion()方法,這兩個方法分別用於異常控制和全部調用完成後的清理的,這裏的異常控制主要是捕獲當前Handler中的異常,而afterCompletion()方法則會保證在全部步驟以後必定會進行調用的,不管是否拋出異常;
  • 對於Handler的使用,咱們但願可以達到的目的是,適用方只須要實現該接口,而且使用某個註解來將其標誌爲Spring的bean便可,而無需管整個Pipeline的組裝和流程控制。經過這種方式,咱們即保留了每一個Spring提供給咱們的便利性,也使用了Pipeline模式的靈活性。

       上述流程代碼中,咱們注意到,每一個層級的方法中都有一個HandlerContext用於傳遞鏈相關的控制信息,這裏咱們來看一下其源碼:

@Component
@Scope("prototype")
public class HandlerContext {

  HandlerContext prev;
  HandlerContext next;
  Handler handler;

  private Task task;

  public void fireTaskReceived(Request request) {
    invokeTaskReceived(next(), request);
  }

  /**
   * 處理接收到任務的事件
   */
  static void invokeTaskReceived(HandlerContext ctx, Request request) {
    if (ctx != null) {
      try {
        ctx.handler().receiveTask(ctx, request);
      } catch (Throwable e) {
        ctx.handler().exceptionCaught(ctx, e);
      }
    }
  }

  public void fireTaskFiltered(Task task) {
    invokeTaskFiltered(next(), task);
  }

  /**
   * 處理任務過濾事件
   */
  static void invokeTaskFiltered(HandlerContext ctx, Task task) {
    if (null != ctx) {
      try {
        ctx.handler().filterTask(ctx, task);
      } catch (Throwable e) {
        ctx.handler().exceptionCaught(ctx, e);
      }
    }
  }

  public void fireTaskExecuted(Task task) {
    invokeTaskExecuted(next(), task);
  }

  /**
   * 處理執行任務事件
   */
  static void invokeTaskExecuted(HandlerContext ctx, Task task) {
    if (null != ctx) {
      try {
        ctx.handler().executeTask(ctx, task);
      } catch (Exception e) {
        ctx.handler().exceptionCaught(ctx, e);
      }
    }
  }

  public void fireAfterCompletion(HandlerContext ctx) {
    invokeAfterCompletion(next());
  }

  static void invokeAfterCompletion(HandlerContext ctx) {
    if (null != ctx) {
      ctx.handler().afterCompletion(ctx);
    }
  }

  private HandlerContext next() {
    return next;
  }

  private Handler handler() {
    return handler;
  }
}

       在HandlerContext中,咱們須要說明以下幾點:

  • 以前Handler接口默認實現的ctx.fireXXX()方法,在這裏都委託給了對應的invokeXXX()方法進行調用,並且咱們須要注意到,在傳遞給invokeXXX()方法的參數裏,傳入的HandlerContext對象都是經過next()方法獲取到的。也就是說咱們在Handler中調用ctx.fireXXX()方法時,都是在調用當前handler的下一個handler對應層級的方法,經過這種方式咱們就實現了鏈的往下傳遞。
  • 在上一點中咱們說到,在某個Handler中若是想讓鏈往下傳遞,只須要調用ctx.fireXXX()方法便可,也就是說,若是咱們在某個Handler中,若是根據業務,當前層級已經調用完成,而無需調用後續的Handler,那麼咱們就不須要調用ctx.fireXXX()方法便可;
  • HandlerContext中,咱們也實現了invokeXXX()方法,該方法的主要做用是供給外部的Pipeline進行調用的,以開啓每一個層級的鏈;
  • 在每一個invokeXXX()方法中,咱們都使用try…catch將當前層級的調用拋出的異常給捕獲了,而後調用ctx.handler().exceptionCaught()方法處理該異常,這也就是咱們前面說的,若是想處理當前Handler中的異常,只須要實現該Handler中的exceptionCaught()方法便可,異常捕獲流程就是在這裏的HandlerContext中進行處理的;
  • HandlerContext的聲明處,咱們須要注意到,其使用了@Component@Scope("prototype")註解進行標註了,這說明咱們的HandlerContext是由Spring所管理的一個bean,而且因爲咱們每個Handler實際上都由一個HandlerContext維護着,因此這裏必須聲明爲prototype類型。經過這種方式,咱們的HandlerContext也就具有了諸如Spring相關的bean的功能,也就可以根據業務需求進行一些額外的處理了;

       前面咱們講解了HandlerHandlerContext的具體實現,以及實現的過程當中須要注意的問題,下面咱們就來看一下進行流程控制的Pipeline是如何實現的,以下是Pipeline接口的定義:

public interface Pipeline {
  
  Pipeline fireTaskReceived();
  
  Pipeline fireTaskFiltered();
  
  Pipeline fireTaskExecuted();
  
  Pipeline fireAfterCompletion();
}

       這裏 主要是定義了一個Pipeline接口,該接口定義了一系列的層級調用,是每一個層級的入口方法。以下是該接口的一個實現類:

@Component("pipeline")
@Scope("prototype")
public class DefaultPipeline implements Pipeline, ApplicationContextAware, InitializingBean {
  // 建立一個默認的handler,將其注入到首尾兩個節點的HandlerContext中,其做用只是將鏈往下傳遞
  private static final Handler DEFAULT_HANDLER = new Handler() {};

  // 將ApplicationContext注入進來的主要緣由在於,HandlerContext是prototype類型的,於是須要
  // 經過ApplicationContext.getBean()方法來獲取其實例
  private ApplicationContext context;

  // 建立一個頭結點和尾節點,這兩個節點內部沒有作任何處理,只是默認的將每一層級的鏈往下傳遞,
  // 這裏頭結點和尾節點的主要做用就是用於標誌整個鏈的首尾,全部的業務節點都在這兩個節點中間
  private HandlerContext head;
  private HandlerContext tail;

  // 用於業務調用的request對象,其內部封裝了業務數據
  private Request request;
  // 用於執行任務的task對象
  private Task task;

  // 最初始的業務數據須要經過構造函數傳入,由於這是驅動整個pipeline所須要的數據,
  // 通常經過外部調用方的參數進行封裝便可
  public DefaultPipeline(Request request) {
    this.request = request;
  }

  // 這裏咱們能夠看到,每一層級的調用都是經過HandlerContext.invokeXXX(head)的方式進行的,
  // 也就是說咱們每一層級鏈的入口都是從頭結點開始的,固然在某些狀況下,咱們也須要從尾節點開始鏈
  // 的調用,這個時候傳入tail便可。
  @Override
  public Pipeline fireTaskReceived() {
    HandlerContext.invokeTaskReceived(head, request);
    return this;
  }

  // 觸發任務過濾的鏈調用
  @Override
  public Pipeline fireTaskFiltered() {
    HandlerContext.invokeTaskFiltered(head, task);
    return this;
  }

  // 觸發任務執行的鏈執行
  @Override
  public Pipeline fireTaskExecuted() {
    HandlerContext.invokeTaskExecuted(head, task);
    return this;
  }

  // 觸發最終完成的鏈的執行
  @Override
  public Pipeline fireAfterCompletion() {
    HandlerContext.invokeAfterCompletion(head);
    return this;
  }
  
  // 用於往Pipeline中添加節點的方法,讀者朋友也能夠實現其餘的方法用於進行鏈的維護
  void addLast(Handler handler) {
    HandlerContext handlerContext = newContext(handler);
    tail.prev.next = handlerContext;
    handlerContext.prev = tail.prev;
    handlerContext.next = tail;
    tail.prev = handlerContext;
  }

  // 這裏經過實現InitializingBean接口來達到初始化Pipeline的目的,能夠看到,這裏初始的時候
  // 咱們經過ApplicationContext實例化了兩個HandlerContext對象,而後將head.next指向tail節點,
  // 將tail.prev指向head節點。也就是說,初始時,整個鏈只有頭結點和尾節點。
  @Override
  public void afterPropertiesSet() throws Exception {
    head = newContext(DEFAULT_HANDLER);
    tail = newContext(DEFAULT_HANDLER);
    head.next = tail;
    tail.prev = head;
  }

  // 使用默認的Handler初始化一個HandlerContext
  private HandlerContext newContext(Handler handler) {
    HandlerContext context = this.context.getBean(HandlerContext.class);
    context.handler = handler;
    return context;
  }

  // 注入ApplicationContext對象
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.context = applicationContext;
  }
}

       關於DefaultPipeline的實現,主要有以下幾點須要說明:

  • DefaultPipeline使用@Component@Scope("prototype")註解進行了標註,前一個註解用於將其聲明爲一個Spring容器所管理的bean,然後一個註解則用於表徵DefaultPipeline是一個多例類型的,很明顯,這裏的Pipeline是有狀態的。這裏須要進行說明的是,"有狀態"主要是由於咱們可能會根據業務狀況動態的調整個鏈的節點狀況,並且這裏的RequestTask對象都是與具體的業務相關的,於是必須聲明爲prototype類型;
  • 上面的示例中,Request對象是經過構造Pipeline對象的時候傳進來的,而Task對象則是在Pipeline的流轉過程當中生成的,這裏好比經過完成fireTaskReceived()鏈的調用以後,就須要經過外部請求Request獲得一個Task對象,從而進行整個Pipeline的後續處理;

       這裏咱們已經實現了PipelineHandlerContextHandler,知道這些bean都是被Spring所管理的bean,那麼咱們接下來的問題主要在於如何進行整個鏈的組裝。這裏的組裝方式比較簡單,其主要須要解決兩個問題:

  • 對於後續寫業務代碼的人而言,其只須要實現一個Handler接口便可,而無需處理與鏈相關的全部邏輯,於是咱們須要獲取到全部實現了Handler接口的bean;
  • 將實現了Handler接口的bean經過HandlerContext進行封裝,而後將其添加到Pipeline中。

       這裏的第一個問題比較好處理,由於經過ApplicationContext就能夠獲取實現了某個接口的全部bean,而第二個問題咱們能夠經過聲明一個實現了BeanPostProcessor接口的類來實現。以下是其實現代碼:

@Component
public class HandlerBeanProcessor implements BeanPostProcessor, ApplicationContextAware {

  private ApplicationContext context;

  // 該方法會在一個bean初始化完成後調用,這裏主要是在Pipeline初始化完成以後獲取全部實現了
  // Handler接口的bean,而後經過調用Pipeline.addLast()方法將其添加到pipeline中
  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (bean instanceof DefaultPipeline) {
      DefaultPipeline pipeline = (DefaultPipeline) bean;
      Map<String, Handler> handlerMap = context.getBeansOfType(Handler.class);
      handlerMap.forEach((name, handler) -> pipeline.addLast(handler));
    }

    return bean;
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.context = applicationContext;
  }
}

       這裏咱們整個鏈的維護工做就已經完成,能夠看到,如今基本上已經實現了前面圖中整個鏈式流程的控制。這裏須要說明的一點是,上面的HandlerBeanProcessor.postProcessAfterInitialization()方法的執行是在InitializingBean.afterPropertySet()方法以後執行的,也就是說這裏HandlerBeanProcessor在執行時,整個Pipeline是已經初始化完成了的。下面咱們來看一下外部客戶端如何進行整個鏈是流程的控制:

@Service
public class ApplicationService {

  @Autowired
  private ApplicationContext context;
  
  public void mockedClient() {
    Request request = new Request();  // request通常是經過外部調用獲取
    Pipeline pipeline = newPipeline(request);
    try {
      pipeline.fireTaskReceived();
      pipeline.fireTaskFiltered();
      pipeline.fireTaskExecuted();
    } finally {
      pipeline.fireAfterCompletion();
    }
  }
  
  private Pipeline newPipeline(Request request) {
    return context.getBean(DefaultPipeline.class, request);
  }
}

       這裏咱們模擬了一個客戶端的調用,首先建立了一個Pipeline對象,而後依次調用其各個層級的方法,而且這裏咱們使用try…finally結構來保證Pipeline.fireAfterCompletion()方法必定會執行。如此咱們就完成了整個責任鏈模式的構造。這裏咱們使用前面用到的時效性過濾的filter來做爲示例來實現一個Handler

@Component
public class DurationHandler implements Handler {

  @Override
  public void filterTask(HandlerContext ctx, Task task) {
    System.out.println("時效性檢驗");
    ctx.fireTaskFiltered(task);
  }
}

       關於這裏的具體業務Handler咱們須要說明的有以下幾點:

  • Handler必須使用@Conponent註解來將其聲明爲Spring容器所管理的一個bean,這樣咱們前面實現的HandlerBeanProcessor才能將其動態的添加到整個Pipeline中;
  • 在每一個Handler中,須要根據當前的業務須要來實現具體的層級方法,好比這裏是進行時效性檢驗,就是"任務過濾"這一層級的邏輯,由於時效性檢驗經過咱們才能執行這個task,於是這裏須要實現的是Handler.filterTask()方法,若是咱們須要實現的是執行task的邏輯,那麼須要實現的就是Handler.executeTask()方法;
  • 在實現完具體的業務邏輯以後,咱們能夠根據當前的業務須要看是否須要將當前層級的鏈繼續往下傳遞,也就是這裏的ctx.fireTaskFiltered(task);方法的調用,咱們能夠看前面HandlerContext.fireXXX()方法就是會獲取當前節點的下一個節點,而後進行調用。若是根據業務須要,不須要將鏈往下傳遞,那麼就不須要調用ctx.fireTaskFiltered(task);

3. 小結

       如此,咱們就經過兩種方式實現了責任鏈模式,並且咱們實現的責任鏈模式都是符合"開-閉"原則的,也就是說後續咱們要爲鏈添加新的節點的時候,只須要根據規範實現相應的接口便可,而無需處理鏈的維護相關的工做。關於第二種實現方式,這裏咱們並無實現鏈節點的順序控制功能,以及如何動態的添加或刪除鏈的節點,更有甚者,若是控制每一個Handler是單例的仍是多例的。固然,有了前面的框架,這些點實現起來也比較簡單,這裏權當起到一個拋磚引玉的做用,讀者朋友可根據本身的須要進行實現。

相關文章
相關標籤/搜索