責任鏈模式
- 引言
- 責任鏈模式定義
- 類圖
- 角色
- 核心
- 示例代碼
- 1、對請求處理者的抽象
- 2、對請求處理者的抽象
- 3、責任鏈的創建
- 責任鏈實作請假案例
- 案例類圖
- 可擴展性
- 純與不純的責任鏈模式
- 純的責任鏈模式
- 不純的責任鏈模式
- 責任鏈模式主要優點
- 職責鏈模式的主要缺點
- 適用場景
- 模擬實作Tomcat中的過濾器機制
- 運行程序如下
- 分析Tomcat 過濾器中的責任鏈模式
- 參考文章
引言
一個事件需要經過多個物件處理是一個挺常見的場景,譬如采購審批流程,請假流程,軟體開發中的例外處理流程,web請求處理流程等各種各樣的流程,可以考慮使用責任鏈模式來實作,
以請假流程為例,一般公司普通員工的請假流程簡化如下:

普通員工發起一個請假申請,當請假天數小于3天時只需要得到主管批準即可;當請假天數大于3天時,主管批準后還需要提交給經理審批,經理審批通過,若請假天數大于7天還需要進一步提交給總經理審批,
使用 if-else 來實作這個請假流程的簡化代碼如下:
public class LeaveApproval
{
public boolean process(String request, int number) {
boolean result = handleByDirector(request); // 主管處理
if (result == false) { // 主管不批準
return false;
} else if (number < 3) { // 主管批準且天數小于 3
return true;
}
result = handleByManager(request); // 準管批準且天數大于等于 3,提交給經理處理
if (result == false) { // 經理不批準
return false;
} else if (number < 7) { // 經理批準且天數小于 7
return true;
}
result = handleByTopManager(request); // 經理批準且天數大于等于 7,提交給總經理處理
if (result == false) { // 總經理不批準
return false;
}
return true; // 總經理最后批準
}
private boolean handleByDirector(String request)
{
// 主管處理該請假申請
if(request.length()>10)
return false;
return true;
}
private boolean handleByManager(String request) {
// 經理處理該請假申請
if(request.length()>5)
return false;
return true;
}
private boolean handleByTopManager(String request) {
// 總經理處理該請假申請
if(request.length()>3)
return false;
return true;
}
}
問題看起來很簡單,三下五除二就搞定,但是該方案存在幾個問題:
-
LeaveApproval類比較龐大,各個上級的審批方法都集中在該類中,違反了 “單一職責原則”,測驗和維護難度大 - 當需要修改該請假流程,譬如增加當天數大于30天時還需提交給董事長處理,必須修改該類源代碼(并重新進行嚴格地測驗),違反了"開閉原則"
- 該流程缺乏靈活性,流程確定后不可再修改(除非修改源代碼),客戶端無法定制流程
使用責任鏈模式可以解決上述問題,
責任鏈模式定義
避免請求發送者與接收者耦合在一起,讓多個物件都有可能接收請求,將這些物件連接成一條鏈,并且沿著這條鏈傳遞請求,直到有物件處理它為止,職責鏈模式是一種物件行為型模式,
責任鏈可以是一條直線、一個環或者一個樹形結構,最常見的職責鏈是直線型,即沿著一條單向的鏈來傳遞請求,如下圖所示,鏈上的每一個物件都是請求處理者,責任鏈模式可以將請求的處理者組織成一條鏈,并讓請求沿著鏈傳遞,由鏈上的處理者對請求進行相應的處理,在此程序中,客戶端實際上無須關心請求的處理細節以及請求的傳遞,只需將請求發送到鏈上即可,從而實作請求發送者和請求處理者解耦,

對責任鏈的理解,關鍵在于對鏈的理解,即包含如下兩點:
- 鏈是一系列節點的集合,在責任鏈中,節點實質上是指請求的處理者;
- 鏈的各節點可靈活拆分再重組,在責任鏈中,實質上就是請求發送者與請求處理者的解耦,
類圖

角色
我們可以從責任鏈模式的結構圖中看到,具體的請求處理者可以有多個,并且所有的請求處理者均具有相同的介面(繼承于同一抽象類), 責任鏈模式主要包含如下兩個角色
-
Handler(抽象處理者):處理請求的介面,一般設計為具有抽象請求處理方法的抽象類,以便于不同的具體處理者進行繼承,從而實作具體的請求處理方法,此外,由于每一個請求處理者的下家還是一個處理者,因此抽象處理者本身還包含了一個本身的參考( successor)作為其對下家的參考,以便將處理者鏈成一條鏈; -
ConcreteHandler(具體處理者):它是抽象處理者的子類,可以處理用戶請求,在具體處理者類中實作了抽象處理者中定義的抽象請求處理方法,在處理請求之前需要進行判斷,看是否有相應的處理權限,如果可以處理請求就處理它,否則將請求轉發給后繼者;在具體處理者中可以訪問鏈中下一個物件,以便請求的轉發,
在責任鏈模式里,由每一個請求處理者物件對其下家的參考而連接起來形成一條請求處理鏈,請求將在這條鏈上一直傳遞,直到鏈上的某一個請求處理者能夠處理此請求,事實上,發出這個請求的客戶端并不知道鏈上的哪一個請求處理者將處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任,
核心
實作責任鏈模式的關鍵核心是: 在抽象類 Handler 里面聚合它自己(持有自身型別的參考),并在 handleRequest 方法里判斷其是否能夠處理請求,若當前處理者無法處理,則設定其后繼者并向下傳遞,直至請求被處理,
示例代碼
1、對請求處理者的抽象
責任鏈模式的核心在于對 請求處理者的抽象,在實作程序中,抽象處理者一般會被設定為 抽象類,其典型實作代碼如下所示:
public abstract class Handler {
// protected :維持對下家的參考
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor=successor;
}
public abstract void handleRequest(String request);
}
上述代碼中,抽象處理者類定義了對下家的參考 (其一般用 protected 進行修飾),以便將請求轉發給下家,從而形成一條請求處理鏈,同時,在抽象處理者類中還宣告了抽象的請求處理方法,以便由子類進行具體實作,
2、對請求處理者的抽象
具體處理者是抽象處理者的子類,具體處理者類的典型代碼如下:
public class ConcreteHandler extends Handler {
public void handleRequest(String request) {
if (請求滿足條件) {
//處理請求
}else {
this.successor.handleRequest(request); //轉發請求
}
}
}
在具體處理類中,通過對請求進行判斷以便做出相應的處理,因此,其一般具有兩大作用:
-
處理請求,不同的具體處理者以不同的形式實作抽象請求處理方法 handleRequest(); -
轉發請求,若該請求超出了當前處理者類的權限,可以將該請求轉發給下家;
3、責任鏈的創建
需要注意的是,責任鏈模式并不創建職責鏈,職責鏈的創建作業必須由系統的其他部分來完成,一般由使用該責任鏈的客戶端創建,職責鏈模式降低了請求的發送者和請求處理者之間的耦合,從而使得多個請求處理者都有機會處理這個請求,
責任鏈實作請假案例
請假資訊類,包含請假人姓名和請假天數
@Data
@AllArgsConstructor
public class LeaveRequest
{
String name;//請假人的姓名
Integer num;//請假天數
}
抽象處理者類 Handler,維護一個 nextHandler 屬性,該屬性為當前處理者的下一個處理者的參考;宣告了抽象方法 process
//抽象處理者
@Data
public abstract class Handler
{
//維護自身參考
protected Handler handler;
//當前處理者的姓名
protected String name;
//傳入當前處理者的姓名
public Handler(String name)
{
this.name=name;
}
//抽象方法,用來處理請假的請求
public abstract Boolean process(LeaveRequest leaveRequest);
}
三個具體處理類,分別實作了抽象處理類的 process 方法
主管:
public class Director extends Handler{
public Director(String name) {
super(name);
}
//處理請假的請求
@Override
public Boolean process(LeaveRequest leaveRequest) {
//亂數大于3,就批準請求
boolean result = (new Random().nextInt(10)) > 3;
String log = "主管: %s,審批:%s的請假申請,請假天數:%d,審批結果:%s";
System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通過":"不通過"));
if(result)//批準
{
//如果請假天數,超過了3天,那么交給上級繼續審批
if(leaveRequest.num>3)
{
return nextHandler.process(leaveRequest);
}
//請假天數小于3,審批通過
return true;
}
//沒有通過審批
return false;
}
}
經理
public class Manager extends Handler{
public Manager(String name) {
super(name);
}
//處理請假的請求
@Override
public Boolean process(LeaveRequest leaveRequest) {
boolean result = (new Random().nextInt(10)) > 3; // 亂數大于3則為批準,否則不批準
String log = "經理: %s,審批:%s的請假申請,請假天數:%d,審批結果:%s";
System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"批準":"不通過"));
if(result)
{
//請假天數過多,還是需要提交到更高的一級去審批
if(leaveRequest.getNum()>7)
{
return nextHandler.process(leaveRequest);
}
//否則直接通過
return true;
}
return false;
}
}
總經理
public class TopManager extends Handler{
public TopManager(String name) {
super(name);
}
@Override
public Boolean process(LeaveRequest leaveRequest) {
//亂數大于3,就批準請求
boolean result = (new Random().nextInt(10)) > 3;
String log = "總經理: %s,審批:%s的請假申請,請假天數:%d,審批結果:%s";
System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通過":"不通過"));
if(result)//批準
{
//默認只有三個處理器,但是如果后續還要加,也需要留個位置
//如果后續繼續添加
if(nextHandler!=null)
{
return nextHandler.process(leaveRequest);
}
return true;
}
//沒有通過審批
return false;
}
}
處理器鏈類:
//處理器鏈
public class HandlerChain
{
//維護第一個處理器
private Handler director=new Director("小忽悠");
//默認有三個處理器
public HandlerChain()
{
//默認有三個處理器鏈
//并且這三個處理器有先后關系
director.nextHandler=new Manager("小朋友");
director.nextHandler.nextHandler=new TopManager("超級大忽悠");
}
//添加一個處理器進集合
public void addHandler(Handler handler)
{
Handler temp=director;
while(temp.nextHandler!=null)
{
temp=temp.nextHandler;
}
temp.nextHandler=handler;
}
//執行處理器鏈
public void process(LeaveRequest leaveRequest)
{
//第一個處理器,如果可以處理器就不需要交給下一個處理器處理了
//否則,繼續交給下一個處理器處理
director.process(leaveRequest);
}
}
客戶端測驗:
public class Client
{
public static void main(String[] args) {
LeaveRequest leaveRequest=new LeaveRequest("大忽悠",10);
HandlerChain handlerChain=new HandlerChain();
handlerChain.process(leaveRequest);
}
}

案例類圖

與上面所給出的類圖不同的是,我通過一個處理器鏈類,把呼叫處理器鏈處理業務邏輯和客戶端分離開來,進一步解耦
可擴展性
- 如果此時審批流程還需要加上一步,就非常方便
- 例如,我們需要增加一個上帝,來對請假流程做最終的處理,那么我們只需要創建一個上帝處理器實作處理器抽象類,然后添加進處理器鏈中即可
public class God extends Handler{
public God(String name) {
super(name);
}
@Override
public Boolean process(LeaveRequest leaveRequest) {
System.out.println("上帝保佑你,所以你可以放假了");
return true;
}
}
客戶端:
public class Client
{
public static void main(String[] args) {
LeaveRequest leaveRequest=new LeaveRequest("大忽悠",10);
HandlerChain handlerChain=new HandlerChain();
handlerChain.addHandler(new God("上帝"));
handlerChain.process(leaveRequest);
}
}

如果還想繼續添加處理器,就需要在上帝process方法中預留一個介面
這樣很麻煩,我這里沒有繼續對方法抽取,進行解耦,感興趣的小伙伴,可以繼續嘗試解耦
純與不純的責任鏈模式
純的責任鏈模式
- 一個具體處理者物件只能在兩個行為中選擇一個:要么承擔全部責任,要么將責任推給下家,不允許出現某一個具體處理者物件在承擔了一部分或全部責任后又將責任向下傳遞的情況
- 一個請求必須被某一個處理者物件所接收,不能出現某個請求未被任何一個處理者物件處理的情況
不純的責任鏈模式
- 允許某個請求被一個具體處理者部分處理后再向下傳遞
- 或者一個具體處理者處理完某請求后其后繼處理者可以繼續處理該請求
- 而且一個請求可以最終不被任何處理者物件所接收
責任鏈模式主要優點
- 物件僅需知道該請求會被處理即可,且鏈中的物件不需要知道鏈的結構,由客戶端負責鏈的創建,降低了系統的耦合度
- 請求處理物件僅需維持一個指向其后繼者的參考,而不需要維持它對所有的候選處理者的參考,可簡化物件的相互連接
- 在給物件分派職責時,職責鏈可以給我們更多的靈活性,可以在運行時對該鏈進行動態的增刪改,改變處理一個請求的職責
- 新增一個新的具體請求處理者時無須修改原有代碼,只需要在客戶端重新建鏈即可,符合 “開閉原則”
職責鏈模式的主要缺點
- 一個請求可能因職責鏈沒有被正確配置而得不到處理
- 對于比較長的職責鏈,請求的處理可能涉及到多個處理物件,系統性能將受到一定影響,且不方便除錯
- 可能因為職責鏈創建不當,造成回圈呼叫,導致系統陷入死回圈
適用場景
- 有多個物件可以處理同一個請求,具體哪個物件處理該請求待運行時刻再確定,客戶端只需將請求提交到鏈上,而無須關心請求的處理物件是誰以及它是如何處理的
- 在不明確指定接收者的情況下,向多個物件中的一個提交一個請求
- 可動態指定一組物件處理請求,客戶端可以動態創建職責鏈來處理請求,還可以改變鏈中處理者之間的先后次序
模擬實作Tomcat中的過濾器機制
定義封裝請求的類Request和封裝處理結果回應的類Response
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Reponse
{
private List<String> data=new ArrayList<>();
public void addData(String data)
{
this.data.add(data);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Request
{
private Object data;
}
第二步:定義具有過濾功能的介面Filter,具體的過濾規則需要實作該介面
/*
* 定義介面Filter,具體的過濾規則需要實作這個介面,最后一個引數添加的意義是我們在Main函式中:
* fc.doFilter(request, response,fc);執行這一步的時候可以按照規則鏈條一次使用三個過濾規則對字串進行處理
*/
public interface Filter
{
void doFilter(Request request,Reponse reponse,FilterChain filterChain);
}
第三步:定義具體的過濾處理規則
public class StuAgeFilter implements Filter
{
@Override
public void doFilter(Request request, Reponse reponse, FilterChain filterChain) {
Stu stu = (Stu) request.getData();
if(stu.getName().contains("忽悠"))
{
//名字不符合要求
reponse.addData("名字不符合要求");
}
//名字符合要求
reponse.addData("名字符合要求");
filterChain.doFilter(request,reponse,filterChain);
}
}
//學生過濾器--過濾出18歲以上的
public class StuFilter implements Filter
{
@Override
public void doFilter(Request request, Reponse reponse, FilterChain filterChain) {
Stu stu = (Stu)request.getData();
if(stu.getAge()<18)
{
//不放行
reponse.addData("年齡不符合要求");
}
//放行
reponse.addData("年齡滿足要求");
filterChain.doFilter(request,reponse,filterChain);
}
}
第四步:定義責任鏈FilterChain
//過濾鏈條
@Data
public class FilterChain
{
//用List集合來存過濾器
private List<Filter> filters = new ArrayList<Filter>();
//用于標記規則的參考順序
private int index;
public FilterChain()
{
//初始化為0
index=0;
}
//往過濾器鏈條中添加新的過濾器
public FilterChain addFilter(Filter f)
{
filters.add(f);
//代碼的設計技巧:Chain鏈添加過濾規則結束后回傳添加后的Chain,方便我們下面doFilter函式的操作
return this;
}
public void doFilter(Request request, Reponse response, FilterChain chain){
//index初始化為0,filters.size()為3,不會執行return操作
//說明所有過濾器都執行完了
if(index==filters.size()){
return;
}
//獲取當前過濾器
Filter f=filters.get(index);
//下一次獲取的時候,就是下一個過濾器了
index++;
//執行當前過濾器的過濾方法
f.doFilter(request, response, chain);
}
}
第五步:測驗
public class Client
{
public static void main(String[] args)
{
//創建請求物件
Request request=new Request();
request.setData(new Stu("小朋友",19));
//創建回應物件
Reponse reponse=new Reponse();
//創建一個過濾器鏈
FilterChain filterChain=new FilterChain();
filterChain.addFilter(new StuAgeFilter());
filterChain.addFilter(new StuFilter());
//執行
filterChain.doFilter(request,reponse,filterChain);
reponse.getData().forEach(x->{
System.out.println(x);
});
}
}

運行程序如下

分析Tomcat 過濾器中的責任鏈模式
Servlet 過濾器是可用于 Servlet 編程的 Java 類,可以實作以下目的:在客戶端的請求訪問后端資源之前,攔截這些請求;在服務器的回應發送回客戶端之前,處理這些回應,
Servlet 定義了過濾器介面 Filter 和過濾器鏈介面 FilterChain 的原始碼如下
public interface Filter {
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
public void destroy();
}
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
我們自定義一個過濾器的步驟是:
1)寫一個過濾器類,實作 javax.servlet.Filter 介面,如下所示
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 做一些自定義處理....
System.out.println("執行doFilter()方法之前...");
chain.doFilter(request, response); // 傳遞請求給下一個過濾器
System.out.println("執行doFilter()方法之后...");
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
2)在 web.xml 檔案中增加該過濾器的配置,譬如下面是攔截所有請求
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.whirly.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
當啟動 Tomcat 是我們的過濾器就可以發揮作用了,那么過濾器是怎樣運行的呢?
Tomcat有Pipeline Valve機制,也是使用了責任鏈模式,一個請求會在Pipeline中流轉,Pipeline會呼叫相應的Valve完成具體的邏輯處理;
其中的一個基礎Valve為StandardWrapperValve,其中的一個作用是呼叫ApplicationFilterFactory生成Filter鏈,具體代碼在invoke方法中
在運行過濾器之前需要完成過濾器的加載和初始化,以及根據配置資訊生成過濾器鏈:
- 過濾器的加載具體是在
ContextConfig類的configureContext方法中,分別加載filter和filterMap的相關資訊,并保存在背景關系環境中 - 過濾器的初始化在
StandardContext類的startInternal方法中完成,保存在filterConfigs中并存到背景關系環境中 - 請求流轉到
StandardWrapperValve時,在invoke方法中,會根據過濾器映射配置資訊,為每個請求創建對ApplicationFilterChain,其中包含了目標Servlet以及對應的過濾器鏈,并呼叫過濾器鏈的doFilter方法執行過濾器
StandardWrapperValve 呼叫 ApplicationFilterFactory 為請求創建過濾器鏈并呼叫過濾器鏈的關鍵代碼如下:
final class StandardWrapperValve extends ValveBase {
public final void invoke(Request request, Response response) throws IOException, ServletException {
// 省略其他的邏輯處理...
// 呼叫 ApplicationFilterChain.createFilterChain() 創建過濾器鏈
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
if (servlet != null && filterChain != null) {
// 省略
} else if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else if (comet) {
filterChain.doFilterEvent(request.getEvent());
} else {
// 呼叫過濾器鏈的 doFilter 方法開始過濾
filterChain.doFilter(request.getRequest(), response.getResponse());
}
過濾器鏈 ApplicationFilterChain 的關鍵代碼如下,過濾器鏈實際是一個 ApplicationFilterConfig 陣列
final class ApplicationFilterChain implements FilterChain, CometFilterChain {
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 過濾器鏈
private Servlet servlet = null; // 目標
// ...
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
// ...
} else {
internalDoFilter(request,response); // 呼叫 internalDoFilter 方法
}
}
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
// 從過濾器陣列中取出當前過濾器配置,然后下標自增1
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter(); // 從過濾器配置中取出該 過濾器物件
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal = ((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
// 呼叫過濾器的 doFilter,完成一個過濾器的過濾功能
filter.doFilter(request, response, this);
}
return; // 這里很重要,不會重復執行后面的 servlet.service(request, response)
}
// 執行完過濾器鏈的所有過濾器之后,呼叫 Servlet 的 service 完成請求的處理
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
if( Globals.IS_SECURITY_ENABLED ) {
} else {
servlet.service(request, response);
}
} else {
servlet.service(request, response);
}
}
// 省略...
}
過濾器
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("執行doFilter()方法之前...");
chain.doFilter(request, response); // 傳遞請求給下一個過濾器
System.out.println("執行doFilter()方法之后...");
}
當下標小于過濾器陣列長度 n 時,說明過濾器鏈未執行完,所以從陣列中取出當前過濾器,呼叫過濾器的 doFilter 方法完成過濾處理,在過濾器的 doFilter 中又呼叫 FilterChain 的 doFilter,回到 ApplicationFilterChain,又繼續根據下標是否小于陣列長度來判斷過濾器鏈是否已執行完,未完則繼續從陣列取出過濾器并呼叫 doFilter 方法,所以這里的過濾鏈是通過嵌套遞回的方式來串成一條鏈,
當全部過濾器都執行完畢,最后一次進入 ApplicationFilterChain.doFilter 方法的時候 pos < n 為false,不進入 if (pos < n) 中,而是執行后面的代碼,判斷 (request instanceof HttpServletRequest) && (response instanceof HttpServletResponse),若為 http 請求則呼叫 servlet.service(request, response); 來處理該請求,
處理完畢之后沿著呼叫過濾器的順序反向退堆疊,分別執行過濾器中 chain.doFilter() 之后的處理邏輯,需要注意的是在 if (pos < n) 方法體的最后有一個 return;,這樣就保證了只有最后一次進入 ApplicationFilterChain.doFilter 方法的呼叫能夠執行后面的 servlet.service(request, response) 方法
畫一個簡要的呼叫堆疊如下所示:

ApplicationFilterChain 類扮演了抽象處理者角色,具體處理者角色由各個 Filter 扮演
參考文章
設計模式之責任鏈模式及典型應用
責任鏈模式綜述(基礎篇)
責任樹模式(責任鏈模式+策略模式的組合)
淺談springMVC中的設計模式(1)——責任鏈模式
責任鏈設計模式----過濾器模擬實作
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/299973.html
標籤:java
