java設(shè)計(jì)模式責(zé)任鏈模式原理案例詳解
引言
以請(qǐng)假流程為例,一般公司普通員工的請(qǐng)假流程簡(jiǎn)化如下:
普通員工發(fā)起一個(gè)請(qǐng)假申請(qǐng),當(dāng)請(qǐng)假天數(shù)小于3天時(shí)只需要得到主管批準(zhǔn)即可;當(dāng)請(qǐng)假天數(shù)大于3天時(shí),主管批準(zhǔn)后還需要提交給經(jīng)理審批,經(jīng)理審批通過(guò),若請(qǐng)假天數(shù)大于7天還需要進(jìn)一步提交給總經(jīng)理審批。
使用 if-else
來(lái)實(shí)現(xiàn)這個(gè)請(qǐng)假流程的簡(jiǎn)化代碼如下:
public class LeaveApproval { public boolean process(String request, int number) { boolean result = handleByDirector(request); // 主管處理 if (result == false) { // 主管不批準(zhǔn) return false; } else if (number < 3) { // 主管批準(zhǔn)且天數(shù)小于 3 return true; } result = handleByManager(request); // 準(zhǔn)管批準(zhǔn)且天數(shù)大于等于 3,提交給經(jīng)理處理 if (result == false) { // 經(jīng)理不批準(zhǔn) return false; } else if (number < 7) { // 經(jīng)理批準(zhǔn)且天數(shù)小于 7 return true; } result = handleByTopManager(request); // 經(jīng)理批準(zhǔn)且天數(shù)大于等于 7,提交給總經(jīng)理處理 if (result == false) { // 總經(jīng)理不批準(zhǔn) return false; } return true; // 總經(jīng)理最后批準(zhǔn) } private boolean handleByDirector(String request) { // 主管處理該請(qǐng)假申請(qǐng) if(request.length()>10) return false; return true; } private boolean handleByManager(String request) { // 經(jīng)理處理該請(qǐng)假申請(qǐng) if(request.length()>5) return false; return true; } private boolean handleByTopManager(String request) { // 總經(jīng)理處理該請(qǐng)假申請(qǐng) if(request.length()>3) return false; return true; } }
問(wèn)題看起來(lái)很簡(jiǎn)單,三下五除二就搞定,但是該方案存在幾個(gè)問(wèn)題:
-
LeaveApproval
類(lèi)比較龐大,各個(gè)上級(jí)的審批方法都集中在該類(lèi)中,違反了 “單一職責(zé)原則”,測(cè)試和維護(hù)難度大 - 當(dāng)需要修改該請(qǐng)假流程,譬如增加當(dāng)天數(shù)大于30天時(shí)還需提交給董事長(zhǎng)處理,必須修改該類(lèi)源代碼(并重新進(jìn)行嚴(yán)格地測(cè)試),違反了"開(kāi)閉原則"
- 該流程缺乏靈活性,流程確定后不可再修改(除非修改源代碼),客戶(hù)端無(wú)法定制流程
使用責(zé)任鏈模式可以解決上述問(wèn)題。
責(zé)任鏈模式定義
避免請(qǐng)求發(fā)送者與接收者耦合在一起,讓多個(gè)對(duì)象都有可能接收請(qǐng)求,將這些對(duì)象連接成一條鏈,并且沿著這條鏈傳遞請(qǐng)求,直到有對(duì)象處理它為止。職責(zé)鏈模式是一種對(duì)象行為型模式。
責(zé)任鏈可以是一條直線(xiàn)、一個(gè)環(huán)或者一個(gè)樹(shù)形結(jié)構(gòu),最常見(jiàn)的職責(zé)鏈?zhǔn)侵本€(xiàn)型,即沿著一條單向的鏈來(lái)傳遞請(qǐng)求,如下圖所示。鏈上的每一個(gè)對(duì)象都是請(qǐng)求處理者,責(zé)任鏈模式可以將請(qǐng)求的處理者組織成一條鏈,并讓請(qǐng)求沿著鏈傳遞,由鏈上的處理者對(duì)請(qǐng)求進(jìn)行相應(yīng)的處理。在此過(guò)程中,客戶(hù)端實(shí)際上無(wú)須關(guān)心請(qǐng)求的處理細(xì)節(jié)以及請(qǐng)求的傳遞,只需將請(qǐng)求發(fā)送到鏈上即可,從而實(shí)現(xiàn)請(qǐng)求發(fā)送者和請(qǐng)求處理者解耦。
對(duì)責(zé)任鏈的理解,關(guān)鍵在于對(duì)鏈的理解,即包含如下兩點(diǎn):
- 鏈?zhǔn)且幌盗泄?jié)點(diǎn)的集合,在責(zé)任鏈中,節(jié)點(diǎn)實(shí)質(zhì)上是指請(qǐng)求的處理者;
- 鏈的各節(jié)點(diǎn)可靈活拆分再重組,在責(zé)任鏈中,實(shí)質(zhì)上就是請(qǐng)求發(fā)送者與請(qǐng)求處理者的解耦。
類(lèi)圖
角色
我們可以從責(zé)任鏈模式的結(jié)構(gòu)圖中看到,具體的請(qǐng)求處理者可以有多個(gè),并且所有的請(qǐng)求處理者均具有相同的接口(繼承于同一抽象類(lèi))。 責(zé)任鏈模式主要包含如下兩個(gè)角色
Handler
(抽象處理者):處理請(qǐng)求的接口,一般設(shè)計(jì)為具有抽象請(qǐng)求處理方法的抽象類(lèi),以便于不同的具體處理者進(jìn)行繼承,從而實(shí)現(xiàn)具體的請(qǐng)求處理方法。此外,由于每一個(gè)請(qǐng)求處理者的下家還是一個(gè)處理者,因此抽象處理者本身還包含了一個(gè)本身的引用( successor)作為其對(duì)下家的引用,以便將處理者鏈成一條鏈;
ConcreteHandler
(具體處理者):它是抽象處理者的子類(lèi),可以處理用戶(hù)請(qǐng)求,在具體處理者類(lèi)中實(shí)現(xiàn)了抽象處理者中定義的抽象請(qǐng)求處理方法,在處理請(qǐng)求之前需要進(jìn)行判斷,看是否有相應(yīng)的處理權(quán)限,如果可以處理請(qǐng)求就處理它,否則將請(qǐng)求轉(zhuǎn)發(fā)給后繼者;在具體處理者中可以訪(fǎng)問(wèn)鏈中下一個(gè)對(duì)象,以便請(qǐng)求的轉(zhuǎn)發(fā)。
在責(zé)任鏈模式里,由每一個(gè)請(qǐng)求處理者對(duì)象對(duì)其下家的引用而連接起來(lái)形成一條請(qǐng)求處理鏈。請(qǐng)求將在這條鏈上一直傳遞,直到鏈上的某一個(gè)請(qǐng)求處理者能夠處理此請(qǐng)求。事實(shí)上,發(fā)出這個(gè)請(qǐng)求的客戶(hù)端并不知道鏈上的哪一個(gè)請(qǐng)求處理者將處理這個(gè)請(qǐng)求,這使得系統(tǒng)可以在不影響客戶(hù)端的情況下動(dòng)態(tài)地重新組織鏈和分配責(zé)任。
核心
實(shí)現(xiàn)責(zé)任鏈模式的關(guān)鍵核心是: 在抽象類(lèi) Handler 里面聚合它自己(持有自身類(lèi)型的引用),并在 handleRequest 方法里判斷其是否能夠處理請(qǐng)求。若當(dāng)前處理者無(wú)法處理,則設(shè)置其后繼者并向下傳遞,直至請(qǐng)求被處理。
示例代碼
1、對(duì)請(qǐng)求處理者的抽象
責(zé)任鏈模式的核心在于對(duì)請(qǐng)求處理者的抽象。在實(shí)現(xiàn)過(guò)程中,抽象處理者一般會(huì)被設(shè)定為抽象類(lèi)
其典型實(shí)現(xiàn)代碼如下所示:
public abstract class Handler { // protected :維持對(duì)下家的引用 protected Handler successor; public void setSuccessor(Handler successor) { this.successor=successor; } public abstract void handleRequest(String request); }
上述代碼中,抽象處理者類(lèi)定義了對(duì)下家的引用 (其一般用 protected 進(jìn)行修飾),以便將請(qǐng)求轉(zhuǎn)發(fā)給下家,從而形成一條請(qǐng)求處理鏈。同時(shí),在抽象處理者類(lèi)中還聲明了抽象的請(qǐng)求處理方法,以便由子類(lèi)進(jìn)行具體實(shí)現(xiàn)。
2、對(duì)請(qǐng)求處理者的抽象
具體處理者是抽象處理者的子類(lèi),具體處理者類(lèi)的典型代碼如下:
public class ConcreteHandler extends Handler { public void handleRequest(String request) { if (請(qǐng)求滿(mǎn)足條件) { //處理請(qǐng)求 }else { this.successor.handleRequest(request); //轉(zhuǎn)發(fā)請(qǐng)求 } } }
在具體處理類(lèi)中,通過(guò)對(duì)請(qǐng)求進(jìn)行判斷以便做出相應(yīng)的處理,因此,其一般具有兩大作用:
- 處理請(qǐng)求,不同的具體處理者以不同的形式實(shí)現(xiàn)抽象請(qǐng)求處理方法 handleRequest();
- 轉(zhuǎn)發(fā)請(qǐng)求,若該請(qǐng)求超出了當(dāng)前處理者類(lèi)的權(quán)限,可以將該請(qǐng)求轉(zhuǎn)發(fā)給下家;
3、責(zé)任鏈的創(chuàng)建
需要注意的是,責(zé)任鏈模式并不創(chuàng)建職責(zé)鏈,職責(zé)鏈的創(chuàng)建工作必須由系統(tǒng)的其他部分來(lái)完成,一般由使用該責(zé)任鏈的客戶(hù)端創(chuàng)建。職責(zé)鏈模式降低了請(qǐng)求的發(fā)送者和請(qǐng)求處理者之間的耦合,從而使得多個(gè)請(qǐng)求處理者都有機(jī)會(huì)處理這個(gè)請(qǐng)求。
責(zé)任鏈實(shí)現(xiàn)請(qǐng)假案例
請(qǐng)假信息類(lèi),包含請(qǐng)假人姓名和請(qǐng)假天數(shù)
@Data @AllArgsConstructor public class LeaveRequest { String name;//請(qǐng)假人的姓名 Integer num;//請(qǐng)假天數(shù) }
抽象處理者類(lèi) Handler
,維護(hù)一個(gè) nextHandler
屬性,該屬性為當(dāng)前處理者的下一個(gè)處理者的引用;聲明了抽象方法 process
//抽象處理者 @Data public abstract class Handler { //維護(hù)自身引用 protected Handler handler; //當(dāng)前處理者的姓名 protected String name; //傳入當(dāng)前處理者的姓名 public Handler(String name) { this.name=name; } //抽象方法,用來(lái)處理請(qǐng)假的請(qǐng)求 public abstract Boolean process(LeaveRequest leaveRequest); }
三個(gè)具體處理類(lèi),分別實(shí)現(xiàn)了抽象處理類(lèi)的 process
方法
主管:
public class Director extends Handler{ public Director(String name) { super(name); } //處理請(qǐng)假的請(qǐng)求 @Override public Boolean process(LeaveRequest leaveRequest) { //隨機(jī)數(shù)大于3,就批準(zhǔn)請(qǐng)求 boolean result = (new Random().nextInt(10)) > 3; String log = "主管: %s,審批:%s的請(qǐng)假申請(qǐng),請(qǐng)假天數(shù):%d,審批結(jié)果:%s"; System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通過(guò)":"不通過(guò)")); if(result)//批準(zhǔn) { //如果請(qǐng)假天數(shù),超過(guò)了3天,那么交給上級(jí)繼續(xù)審批 if(leaveRequest.num>3) { return nextHandler.process(leaveRequest); } //請(qǐng)假天數(shù)小于3,審批通過(guò) return true; } //沒(méi)有通過(guò)審批 return false; } }
經(jīng)理
public class Manager extends Handler{ public Manager(String name) { super(name); } //處理請(qǐng)假的請(qǐng)求 @Override public Boolean process(LeaveRequest leaveRequest) { boolean result = (new Random().nextInt(10)) > 3; // 隨機(jī)數(shù)大于3則為批準(zhǔn),否則不批準(zhǔn) String log = "經(jīng)理: %s,審批:%s的請(qǐng)假申請(qǐng),請(qǐng)假天數(shù):%d,審批結(jié)果:%s"; System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"批準(zhǔn)":"不通過(guò)")); if(result) { //請(qǐng)假天數(shù)過(guò)多,還是需要提交到更高的一級(jí)去審批 if(leaveRequest.getNum()>7) { return nextHandler.process(leaveRequest); } //否則直接通過(guò) return true; } return false; } }
總經(jīng)理
public class TopManager extends Handler{ public TopManager(String name) { super(name); } @Override public Boolean process(LeaveRequest leaveRequest) { //隨機(jī)數(shù)大于3,就批準(zhǔn)請(qǐng)求 boolean result = (new Random().nextInt(10)) > 3; String log = "總經(jīng)理: %s,審批:%s的請(qǐng)假申請(qǐng),請(qǐng)假天數(shù):%d,審批結(jié)果:%s"; System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通過(guò)":"不通過(guò)")); if(result)//批準(zhǔn) { //默認(rèn)只有三個(gè)處理器,但是如果后續(xù)還要加,也需要留個(gè)位置 //如果后續(xù)繼續(xù)添加 if(nextHandler!=null) { return nextHandler.process(leaveRequest); } return true; } //沒(méi)有通過(guò)審批 return false; } }
處理器鏈類(lèi):
//處理器鏈 public class HandlerChain { //維護(hù)第一個(gè)處理器 private Handler director=new Director("小忽悠"); //默認(rèn)有三個(gè)處理器 public HandlerChain() { //默認(rèn)有三個(gè)處理器鏈 //并且這三個(gè)處理器有先后關(guān)系 director.nextHandler=new Manager("小朋友"); director.nextHandler.nextHandler=new TopManager("超級(jí)大忽悠"); } //添加一個(gè)處理器進(jìn)集合 public void addHandler(Handler handler) { Handler temp=director; while(temp.nextHandler!=null) { temp=temp.nextHandler; } temp.nextHandler=handler; } //執(zhí)行處理器鏈 public void process(LeaveRequest leaveRequest) { //第一個(gè)處理器,如果可以處理器就不需要交給下一個(gè)處理器處理了 //否則,繼續(xù)交給下一個(gè)處理器處理 director.process(leaveRequest); } }
客戶(hù)端測(cè)試:
public class Client { public static void main(String[] args) { LeaveRequest leaveRequest=new LeaveRequest("大忽悠",10); HandlerChain handlerChain=new HandlerChain(); handlerChain.process(leaveRequest); } }
案例類(lèi)圖
與上面所給出的類(lèi)圖不同的是,我通過(guò)一個(gè)處理器鏈類(lèi),把調(diào)用處理器鏈處理業(yè)務(wù)邏輯和客戶(hù)端分離開(kāi)來(lái),進(jìn)一步解耦
可擴(kuò)展性
如果此時(shí)審批流程還需要加上一步,就非常方便
例如,我們需要增加一個(gè)上帝,來(lái)對(duì)請(qǐng)假流程做最終的處理,那么我們只需要?jiǎng)?chuàng)建一個(gè)上帝處理器實(shí)現(xiàn)處理器抽象類(lèi),然后添加進(jìn)處理器鏈中即可
public class God extends Handler{ public God(String name) { super(name); } @Override public Boolean process(LeaveRequest leaveRequest) { System.out.println("上帝保佑你,所以你可以放假了"); return true; } }
客戶(hù)端:
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); } }
如果還想繼續(xù)添加處理器,就需要在上帝process方法中預(yù)留一個(gè)接口
這樣很麻煩,我這里沒(méi)有繼續(xù)對(duì)方法抽取,進(jìn)行解耦,感興趣的小伙伴,可以繼續(xù)嘗試解耦
純與不純的責(zé)任鏈模式
純的責(zé)任鏈模式
- 一個(gè)具體處理者對(duì)象只能在兩個(gè)行為中選擇一個(gè):要么承擔(dān)全部責(zé)任,要么將責(zé)任推給下家,不允許出現(xiàn)某一個(gè)具體處理者對(duì)象在承擔(dān)了一部分或全部責(zé)任后又將責(zé)任向下傳遞的情況
- 一個(gè)請(qǐng)求必須被某一個(gè)處理者對(duì)象所接收,不能出現(xiàn)某個(gè)請(qǐng)求未被任何一個(gè)處理者對(duì)象處理的情況
不純的責(zé)任鏈模式
- 允許某個(gè)請(qǐng)求被一個(gè)具體處理者部分處理后再向下傳遞
- 或者一個(gè)具體處理者處理完某請(qǐng)求后其后繼處理者可以繼續(xù)處理該請(qǐng)求
- 而且一個(gè)請(qǐng)求可以最終不被任何處理者對(duì)象所接收
責(zé)任鏈模式主要優(yōu)點(diǎn)
- 對(duì)象僅需知道該請(qǐng)求會(huì)被處理即可,且鏈中的對(duì)象不需要知道鏈的結(jié)構(gòu),由客戶(hù)端負(fù)責(zé)鏈的創(chuàng)建,降低了系統(tǒng)的耦合度
- 請(qǐng)求處理對(duì)象僅需維持一個(gè)指向其后繼者的引用,而不需要維持它對(duì)所有的候選處理者的引用,可簡(jiǎn)化對(duì)象的相互連接
- 在給對(duì)象分派職責(zé)時(shí),職責(zé)鏈可以給我們更多的靈活性,可以在運(yùn)行時(shí)對(duì)該鏈進(jìn)行動(dòng)態(tài)的增刪改,改變處理一個(gè)請(qǐng)求的職責(zé)
- 新增一個(gè)新的具體請(qǐng)求處理者時(shí)無(wú)須修改原有代碼,只需要在客戶(hù)端重新建鏈即可,符合 “開(kāi)閉原則”
職責(zé)鏈模式的主要缺點(diǎn)
- 一個(gè)請(qǐng)求可能因職責(zé)鏈沒(méi)有被正確配置而得不到處理
- 對(duì)于比較長(zhǎng)的職責(zé)鏈,請(qǐng)求的處理可能涉及到多個(gè)處理對(duì)象,系統(tǒng)性能將受到一定影響,且不方便調(diào)試
- 可能因?yàn)槁氊?zé)鏈創(chuàng)建不當(dāng),造成循環(huán)調(diào)用,導(dǎo)致系統(tǒng)陷入死循環(huán)
適用場(chǎng)景
- 有多個(gè)對(duì)象可以處理同一個(gè)請(qǐng)求,具體哪個(gè)對(duì)象處理該請(qǐng)求待運(yùn)行時(shí)刻再確定,客戶(hù)端只需將請(qǐng)求提交到鏈上,而無(wú)須關(guān)心請(qǐng)求的處理對(duì)象是誰(shuí)以及它是如何處理的
- 在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求
- 可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求,客戶(hù)端可以動(dòng)態(tài)創(chuàng)建職責(zé)鏈來(lái)處理請(qǐng)求,還可以改變鏈中處理者之間的先后次序
模擬實(shí)現(xiàn)Tomcat中的過(guò)濾器機(jī)制
第一步:定義封裝請(qǐng)求的類(lèi)Request和封裝處理結(jié)果響應(yīng)的類(lèi)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; }
第二步:定義具有過(guò)濾功能的接口Filter,具體的過(guò)濾規(guī)則需要實(shí)現(xiàn)該接口
/* * 定義接口Filter,具體的過(guò)濾規(guī)則需要實(shí)現(xiàn)這個(gè)接口,最后一個(gè)參數(shù)添加的意義是我們?cè)贛ain函數(shù)中: * fc.doFilter(request, response,fc);執(zhí)行這一步的時(shí)候可以按照規(guī)則鏈條一次使用三個(gè)過(guò)濾規(guī)則對(duì)字符串進(jìn)行處理 */ public interface Filter { void doFilter(Request request,Reponse reponse,FilterChain filterChain); }
第三步:定義具體的過(guò)濾處理規(guī)則
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); } } //學(xué)生過(guò)濾器--過(guò)濾出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("年齡滿(mǎn)足要求"); filterChain.doFilter(request,reponse,filterChain); } }
第四步:定義責(zé)任鏈FilterChain
//過(guò)濾鏈條 @Data public class FilterChain { //用List集合來(lái)存過(guò)濾器 private List<Filter> filters = new ArrayList<Filter>(); //用于標(biāo)記規(guī)則的引用順序 private int index; public FilterChain() { //初始化為0 index=0; } //往過(guò)濾器鏈條中添加新的過(guò)濾器 public FilterChain addFilter(Filter f) { filters.add(f); //代碼的設(shè)計(jì)技巧:Chain鏈添加過(guò)濾規(guī)則結(jié)束后返回添加后的Chain,方便我們下面doFilter函數(shù)的操作 return this; } public void doFilter(Request request, Reponse response, FilterChain chain){ //index初始化為0,filters.size()為3,不會(huì)執(zhí)行return操作 //說(shuō)明所有過(guò)濾器都執(zhí)行完了 if(index==filters.size()){ return; } //獲取當(dāng)前過(guò)濾器 Filter f=filters.get(index); //下一次獲取的時(shí)候,就是下一個(gè)過(guò)濾器了 index++; //執(zhí)行當(dāng)前過(guò)濾器的過(guò)濾方法 f.doFilter(request, response, chain); } }
第五步:測(cè)試
public class Client { public static void main(String[] args) { //創(chuàng)建請(qǐng)求對(duì)象 Request request=new Request(); request.setData(new Stu("小朋友",19)); //創(chuàng)建響應(yīng)對(duì)象 Reponse reponse=new Reponse(); //創(chuàng)建一個(gè)過(guò)濾器鏈 FilterChain filterChain=new FilterChain(); filterChain.addFilter(new StuAgeFilter()); filterChain.addFilter(new StuFilter()); //執(zhí)行 filterChain.doFilter(request,reponse,filterChain); reponse.getData().forEach(x->{ System.out.println(x); }); } }
運(yùn)行過(guò)程如下
分析Tomcat 過(guò)濾器中的責(zé)任鏈模式
Servlet
過(guò)濾器是可用于 Servlet
編程的 Java
類(lèi),可以實(shí)現(xiàn)以下目的:在客戶(hù)端的請(qǐng)求訪(fǎng)問(wèn)后端資源之前,攔截這些請(qǐng)求;在服務(wù)器的響應(yīng)發(fā)送回客戶(hù)端之前,處理這些響應(yīng)。
Servlet
定義了過(guò)濾器接口 Filter
和過(guò)濾器鏈接口 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; }
我們自定義一個(gè)過(guò)濾器的步驟是:
1)寫(xiě)一個(gè)過(guò)濾器類(lèi),實(shí)現(xiàn) javax.servlet.Filter
接口,如下所示
public class MyFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 做一些自定義處理.... System.out.println("執(zhí)行doFilter()方法之前..."); chain.doFilter(request, response); // 傳遞請(qǐng)求給下一個(gè)過(guò)濾器 System.out.println("執(zhí)行doFilter()方法之后..."); } @Override public void destroy() { } @Override public void init(FilterConfig filterConfig) throws ServletException { } }
2)在 web.xml
文件中增加該過(guò)濾器的配置,譬如下面是攔截所有請(qǐng)求
<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>
當(dāng)啟動(dòng) Tomcat 是我們的過(guò)濾器就可以發(fā)揮作用了。那么過(guò)濾器是怎樣運(yùn)行的呢?
Tomcat
有 Pipeline Valve
機(jī)制,也是使用了責(zé)任鏈模式
,一個(gè)請(qǐng)求會(huì)在 Pipeline
中流轉(zhuǎn),Pipeline
會(huì)調(diào)用相應(yīng)的 Valve
完成具體的邏輯處理;
其中的一個(gè)基礎(chǔ)Valve
為 StandardWrapperValve
,其中的一個(gè)作用是調(diào)用 ApplicationFilterFactory
生成 Filter
鏈,具體代碼在 invoke
方法中
在運(yùn)行過(guò)濾器之前需要完成過(guò)濾器的加載和初始化,以及根據(jù)配置信息生成過(guò)濾器鏈:
- 過(guò)濾器的加載具體是在
ContextConfig
類(lèi)的configureContext
方法中,分別加載filter
和filterMap
的相關(guān)信息,并保存在上下文環(huán)境中 - 過(guò)濾器的初始化在
StandardContext
類(lèi)的startInternal
方法中完成,保存在filterConfigs
中并存到上下文環(huán)境中 - 請(qǐng)求流轉(zhuǎn)到
StandardWrapperValve
時(shí),在invoke
方法中,會(huì)根據(jù)過(guò)濾器映射配置信息,為每個(gè)請(qǐng)求創(chuàng)建對(duì)ApplicationFilterChain
,其中包含了目標(biāo)Servlet
以及對(duì)應(yīng)的過(guò)濾器鏈,并調(diào)用過(guò)濾器鏈的doFilter
方法執(zhí)行過(guò)濾器
StandardWrapperValve
調(diào)用 ApplicationFilterFactory
為請(qǐng)求創(chuàng)建過(guò)濾器鏈并調(diào)用過(guò)濾器鏈的關(guān)鍵代碼如下:
final class StandardWrapperValve extends ValveBase { public final void invoke(Request request, Response response) throws IOException, ServletException { // 省略其他的邏輯處理... // 調(diào)用 ApplicationFilterChain.createFilterChain() 創(chuàng)建過(guò)濾器鏈 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 { // 調(diào)用過(guò)濾器鏈的 doFilter 方法開(kāi)始過(guò)濾 filterChain.doFilter(request.getRequest(), response.getResponse()); }
過(guò)濾器鏈 ApplicationFilterChain
的關(guān)鍵代碼如下,過(guò)濾器鏈實(shí)際是一個(gè) ApplicationFilterConfig
數(shù)組
final class ApplicationFilterChain implements FilterChain, CometFilterChain { private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 過(guò)濾器鏈 private Servlet servlet = null; // 目標(biāo) // ... @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if( Globals.IS_SECURITY_ENABLED ) { // ... } else { internalDoFilter(request,response); // 調(diào)用 internalDoFilter 方法 } } private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { // Call the next filter if there is one if (pos < n) { // 從過(guò)濾器數(shù)組中取出當(dāng)前過(guò)濾器配置,然后下標(biāo)自增1 ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = null; try { filter = filterConfig.getFilter(); // 從過(guò)濾器配置中取出該 過(guò)濾器對(duì)象 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 { // 調(diào)用過(guò)濾器的 doFilter,完成一個(gè)過(guò)濾器的過(guò)濾功能 filter.doFilter(request, response, this); } return; // 這里很重要,不會(huì)重復(fù)執(zhí)行后面的 servlet.service(request, response) } // 執(zhí)行完過(guò)濾器鏈的所有過(guò)濾器之后,調(diào)用 Servlet 的 service 完成請(qǐng)求的處理 if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) { if( Globals.IS_SECURITY_ENABLED ) { } else { servlet.service(request, response); } } else { servlet.service(request, response); } } // 省略... }
過(guò)濾器
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("執(zhí)行doFilter()方法之前..."); chain.doFilter(request, response); // 傳遞請(qǐng)求給下一個(gè)過(guò)濾器 System.out.println("執(zhí)行doFilter()方法之后..."); }
當(dāng)下標(biāo)小于過(guò)濾器數(shù)組長(zhǎng)度 n
時(shí),說(shuō)明過(guò)濾器鏈未執(zhí)行完,所以從數(shù)組中取出當(dāng)前過(guò)濾器,調(diào)用過(guò)濾器的 doFilter
方法完成過(guò)濾處理,在過(guò)濾器的 doFilter
中又調(diào)用 FilterChain
的 doFilter
,回到 ApplicationFilterChain
,又繼續(xù)根據(jù)下標(biāo)是否小于數(shù)組長(zhǎng)度來(lái)判斷過(guò)濾器鏈?zhǔn)欠褚褕?zhí)行完,未完則繼續(xù)從數(shù)組取出過(guò)濾器并調(diào)用 doFilter
方法,所以這里的過(guò)濾鏈?zhǔn)峭ㄟ^(guò)嵌套遞歸的方式來(lái)串成一條鏈。
當(dāng)全部過(guò)濾器都執(zhí)行完畢,最后一次進(jìn)入 ApplicationFilterChain.doFilter
方法的時(shí)候 pos < n
為false
,不進(jìn)入 if (pos < n)
中,而是執(zhí)行后面的代碼,判斷 (request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)
,若為 http
請(qǐng)求則調(diào)用 servlet.service(request, response);
來(lái)處理該請(qǐng)求。
處理完畢之后沿著調(diào)用過(guò)濾器的順序反向退棧
,分別執(zhí)行過(guò)濾器中 chain.doFilter()
之后的處理邏輯,需要注意的是在 if (pos < n)
方法體的最后有一個(gè) return
;,這樣就保證了只有最后一次進(jìn)入 ApplicationFilterChain.doFilter
方法的調(diào)用能夠執(zhí)行后面的 servlet.service(request, response)
方法
畫(huà)一個(gè)簡(jiǎn)要的調(diào)用棧如下所示:
ApplicationFilterChain
類(lèi)扮演了抽象處理者角色,具體處理者角色由各個(gè) Filter
扮演
以上就是java設(shè)計(jì)模式責(zé)任鏈模式原理案例詳解的詳細(xì)內(nèi)容,更多關(guān)于java設(shè)計(jì)模式責(zé)任鏈模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java web支持jsonp的實(shí)現(xiàn)代碼
這篇文章主要介紹了java web支持jsonp的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11Feign如何使用protobuf的類(lèi)作為參數(shù)調(diào)用
這篇文章主要介紹了Feign如何使用protobuf的類(lèi)作為參數(shù)調(diào)用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03SpringBoot Logback日志記錄到數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot Logback日志記錄到數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11深入了解Java線(xiàn)程池:從設(shè)計(jì)思想到源碼解讀
這篇文章將從設(shè)計(jì)思想到源碼解讀,帶大家深入了解Java的線(xiàn)程池,文中的示例代碼講解詳細(xì),對(duì)我們的學(xué)習(xí)或工作有一定的幫助,需要的可以參考一下2021-12-12Java加載本地庫(kù)的方法之System.load與System.loadLibrary
最近在做的工作要用到本地方法,所以下面這篇文章主要介紹了Java加載本地庫(kù)的方法之System.load與System.loadLibrary的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09SpringBoot Mybatis如何配置多數(shù)據(jù)源并分包
這篇文章主要介紹了SpringBoot Mybatis如何配置多數(shù)據(jù)源并分包,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05Spring實(shí)戰(zhàn)之獲得Bean本身的id操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之獲得Bean本身的id操作,結(jié)合實(shí)例形式分析了spring獲取Bean本身id的相關(guān)配置與實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-11-11