本文的主要內容:java
更多內容可訪問個人我的博客:laijianfeng.org
關注【小旋鋒】微信公衆號,及時接收博文推送web
一個事件須要通過多個對象處理是一個挺常見的場景,譬如採購審批流程,請假流程,軟件開發中的異常處理流程,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; // 總經理最後批准
}
public boolean handleByDirector(String request) {
// 主管處理該請假申請
}
public boolean handleByManager(String request) {
// 經理處理該請假申請
}
public boolean handleByTopManager(String request) {
// 總經理處理該請假申請
}
}
複製代碼
問題看起來很簡單,三下五除二就搞定,可是該方案存在幾個問題:bash
LeaveApproval
類比較龐大,各個上級的審批方法都集中在該類中,違反了 "單一職責原則",測試和維護難度大服務器
當須要修改該請假流程,譬如增長當天數大於30天時還需提交給董事長處理,必須修改該類源代碼(並從新進行嚴格地測試),違反了 "開閉原則"微信
該流程缺少靈活性,流程肯定後不可再修改(除非修改源代碼),客戶端沒法定製流程app
使用責任鏈模式能夠解決上述問題。
責任鏈模式(Chain of Responsibility Pattern):避免請求發送者與接收者耦合在一塊兒,讓多個對象都有可能接收請求,將這些對象鏈接成一條鏈,而且沿着這條鏈傳遞請求,直到有對象處理它爲止。職責鏈模式是一種對象行爲型模式。
Handler(抽象處理者):它定義了一個處理請求的接口,通常設計爲抽象類,因爲不一樣的具體處理者處理請求的方式不一樣,所以在其中定義了抽象請求處理方法。由於每個處理者的下家仍是一個處理者,所以在抽象處理者中定義了一個抽象處理者類型的對象,做爲其對下家的引用。經過該引用,處理者能夠連成一條鏈。
ConcreteHandler(具體處理者):它是抽象處理者的子類,能夠處理用戶請求,在具體處理者類中實現了抽象處理者中定義的抽象請求處理方法,在處理請求以前須要進行判斷,看是否有相應的處理權限,若是能夠處理請求就處理它,不然將請求轉發給後繼者;在具體處理者中能夠訪問鏈中下一個對象,以便請求的轉發。
類圖以下所示:
純的責任鏈模式:
不純的責任鏈模式:
使用責任鏈模式(不純)重構請假流程
請假信息類,包含請假人姓名和請假天數
@Data
@AllArgsConstructor
public class LeaveRequest {
private String name; // 請假人姓名
private int numOfDays; // 請假天數
}
複製代碼
抽象處理者類 Handler,維護一個 nextHandler
屬性,該屬性爲當前處理者的下一個處理者的引用;聲明瞭抽象方法 process
@Data
public abstract class Handler {
protected String name; // 處理者姓名
protected Handler nextHandler; // 下一個處理者
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) {
boolean result = (new Random().nextInt(10)) > 3; // 隨機數大於3則爲批准,不然不批准
String log = "主管<%s> 審批 <%s> 的請假申請,請假天數: <%d> ,審批結果:<%s> ";
System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));
if (result == false) { // 不批准
return false;
} else if (leaveRequest.getNumOfDays() < 3) { // 批准且天數小於3,返回true
return true;
}
return nextHandler.process(leaveRequest); // 批准且天數大於等於3,提交給下一個處理者處理
}
}
// 經理
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, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));
if (result == false) { // 不批准
return false;
} else if (leaveRequest.getNumOfDays() < 7) { // 批准且天數小於7
return true;
}
return nextHandler.process(leaveRequest); // 批准且天數大於等於7,提交給下一個處理者處理
}
}
// 總經理
public class TopManager extends Handler {
public TopManager(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, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));
if (result == false) { // 總經理不批准
return false;
}
return true; // 總經理最後批准
}
}
複製代碼
客戶端測試
public class Client {
public static void main(String[] args) {
Handler zhangsan = new Director("張三");
Handler lisi = new Manager("李四");
Handler wangwu = new TopManager("王五");
// 建立責任鏈
zhangsan.setNextHandler(lisi);
lisi.setNextHandler(wangwu);
// 發起請假申請
boolean result1 = zhangsan.process(new LeaveRequest("小旋鋒", 1));
System.out.println("最終結果:" + result1 + "\n");
boolean result2 = zhangsan.process(new LeaveRequest("小旋鋒", 4));
System.out.println("最終結果:" + result2 + "\n");
boolean result3 = zhangsan.process(new LeaveRequest("小旋鋒", 8));
System.out.println("最終結果:" + result3 + "\n");
}
}
複製代碼
可能的結果以下:(因爲是否批准是經過隨機數模擬的,因此你的結果可能跟我不一樣)
主管<張三> 審批 <小旋鋒> 的請假申請,請假天數: <1> ,審批結果:<批准>
最終結果:true
主管<張三> 審批 <小旋鋒> 的請假申請,請假天數: <4> ,審批結果:<不批准>
最終結果:false
主管<張三> 審批 <小旋鋒> 的請假申請,請假天數: <8> ,審批結果:<批准>
經理<李四> 審批 <小旋鋒> 的請假申請,請假天數: <8> ,審批結果:<批准>
總經理<王五> 審批 <小旋鋒> 的請假申請,請假天數: <8> ,審批結果:<批准>
最終結果:true
複製代碼
類圖以下所示
對象僅需知道該請求會被處理便可,且鏈中的對象不須要知道鏈的結構,由客戶端負責鏈的建立,下降了系統的耦合度
請求處理對象僅需維持一個指向其後繼者的引用,而不須要維持它對全部的候選處理者的引用,可簡化對象的相互鏈接
在給對象分派職責時,職責鏈能夠給咱們更多的靈活性,能夠在運行時對該鏈進行動態的增刪改,改變處理一個請求的職責
新增一個新的具體請求處理者時無須修改原有代碼,只須要在客戶端從新建鏈便可,符合 "開閉原則"
一個請求可能因職責鏈沒有被正確配置而得不處處理
對於比較長的職責鏈,請求的處理可能涉及到多個處理對象,系統性能將受到必定影響,且不方便調試
可能由於職責鏈建立不當,形成循環調用,致使系統陷入死循環
有多個對象能夠處理同一個請求,具體哪一個對象處理該請求待運行時刻再肯定,客戶端只需將請求提交到鏈上,而無須關心請求的處理對象是誰以及它是如何處理的
在不明確指定接收者的狀況下,向多個對象中的一個提交一個請求
可動態指定一組對象處理請求,客戶端能夠動態建立職責鏈來處理請求,還能夠改變鏈中處理者之間的前後次序
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
扮演
其餘的責任鏈模式的應用基本都是大同小異
這裏列舉幾個典型應用:
Pipeline
和 ChannelHandler
經過責任鏈設計模式來組織代碼邏輯參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+內存分析
責任鏈設計模式(過濾器、攔截器)
歡迎評論、轉發、分享,您的支持是我最大的動力
設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
設計模式 | 裝飾者模式及典型應用
設計模式 | 適配器模式及典型應用
設計模式 | 享元模式及典型應用
設計模式 | 組合模式及典型應用
設計模式 | 模板方法模式及典型應用
設計模式 | 迭代器模式及典型應用
設計模式 | 策略模式及典型應用
設計模式 | 觀察者模式及典型應用
設計模式 | 備忘錄模式及典型應用
設計模式 | 中介者模式及典型應用