關於責任鏈模式,其有兩種形式,一種是通過外部調用的方式對鏈的各個節點調用進行控制,從而進行鏈的各個節點之間的切換;另一種是鏈的每個節點自由控制是否繼續往下傳遞鏈的進度,這種比較典型的使用方式就是Netty中的責任鏈模式。本文主要講解我們如何在Spring中使用這兩種責任鏈模式。
1. 外部控制模式
對於外部控制的方式,這種方式比較簡單,鏈的每個節點只需要專注於各自的邏輯即可,而當前節點調用完成之后是否繼續調用下一個節點,這個則由外部控制邏輯進行。這里我們以一個過濾器的實現邏輯為例進行講解,在平常工作中,我們經常需要根據一系列的條件對某個東西進行過濾,比如任務服務的設計,在執行某個任務時,其需要經過諸如時效性檢驗,風控攔截,任務完成次數等過濾條件的檢驗之后才能判斷當前任務是否能夠執行,只有在所有的過濾條件都完成之后,我們才能執行該任務。那么這里我們就可以抽象出一個Filter
接口,其設計如下:
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。下面是我們的控制邏輯:
@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即可。
這種責任鏈設計方式的優點在於鏈的控制比較簡單,只需要實現一個統一的接口即可,其基本上能夠滿足大部分的邏輯控制,但是對於某些需要動態調整鏈的需求其就無能為力了。比如在執行到某個節點之后需要動態的判斷是否執行下一個節點,或者說要執行某些分叉的節點等等。這個時候我們就需要將鏈節點的傳遞工作交由各個節點進行。
2. 節點控制模式
對於節點控制調用的方式,其主要有三個控制點:Handler,HandlerContext和Pipeline。Handler中是用於編寫具體的業務代碼的;HandlerContext則主要是用於對Handler進行包裹,並且用於控制進行下一個節點的調用的;Pipeline則主要是用於控制整體的流程調用的,比如對於任務的執行,其有任務的查詢,任務的過濾和執行任務等等流程,這些流程整體的邏輯控制就是由Pipeline來控制的,在每個流程中又包含了一系列的子流程,這些子流程則是由一個個的HandlerContext和Handler進行梳理的。這種責任鏈的控制方式整體邏輯如下圖所示:
從圖中可以看出,我們將整個流程通過Pipeline
對象進行了抽象,這里主要分為了三個步驟:查詢task,過濾task和執行task。在每個步驟中,我們都使用了一系列的鏈式調用。圖中需要注意的是,在每次調用鏈的下一個節點的時候,我們都是通過具體的Handler進行的,也就是說是否進行鏈的下一個節點的調用,我們是通過業務實現方來進行動態控制的。
關於該模式的設計,我們首先需要強調的就是Handler
接口的設計,其設計如下所示:
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
主要有如下幾點需要說明:
-
在前面圖中
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的功能,也就能夠根據業務需求進行一些額外的處理了;
前面我們講解了Handler
和HandlerContext
的具體實現,以及實現的過程中需要注意的問題,下面我們就來看一下進行流程控制的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