java設計模式責任鏈模式原理案例詳解
引言
以請假流程為例,一般公司普通員工的請假流程簡化如下:
普通員工發起一個請假申請,當請假天數小於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
扮演
以上就是java設計模式責任鏈模式原理案例詳解的詳細內容,更多關於java設計模式責任鏈模式的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- springboot過濾器和攔截器的實例代碼
- Java中過濾器 (Filter) 和 攔截器 (Interceptor)的使用
- Java中使用Filter過濾器的方法
- Spring攔截器和過濾器的區別在哪?
- java web項目Session獲取不到問題及解決