點贊再看,養(yǎng)成習(xí)慣淋硝,公眾號搜一搜【一角錢技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章雹熬。本文 GitHub org_hejianhui/JavaStudy 已收錄,有我的系列文章谣膳。
前言
- 23種設(shè)計模式速記
- 單例(singleton)模式
- 工廠方法(factory method)模式
- 抽象工廠(abstract factory)模式
- 建造者/構(gòu)建器(builder)模式
- 原型(prototype)模式
- 享元(flyweight)模式
- 外觀(facade)模式
- 適配器(adapter)模式
- 裝飾(decorator)模式
- 觀察者(observer)模式
- 策略(strategy)模式
- 橋接(bridge)模式
- 模板方法(template method)模式
- 持續(xù)更新中......
23種設(shè)計模式快速記憶的請看上面第一篇竿报,本篇和大家一起來學(xué)習(xí)責任/職責鏈模式相關(guān)內(nèi)容。
模式定義
為了避免請求發(fā)送者與多個請求處理者耦合在一起继谚,將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈烈菌;當有請求發(fā)生時,可將請求沿著這條鏈傳遞花履,直到有對象處理它為止芽世。
注意:責任鏈模式也叫職責鏈模式。
在責任鏈模式中诡壁,客戶只需要將請求發(fā)送到責任鏈上即可济瓢,無須關(guān)心請求的處理細節(jié)和請求的傳遞過程,所以責任鏈將請求的發(fā)送者和請求的處理者解耦了妹卿。
模版實現(xiàn)如下:
package com.niuh.designpattern.chainofresponsibility.v1;
/**
* 責任鏈/職責鏈模式
*/
public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
//組裝責任鏈
Handler handler1=new ConcreteHandler1();
Handler handler2=new ConcreteHandler2();
handler1.setNext(handler2);
//提交請求
handler1.handleRequest("two");
}
}
//抽象處理者角色
abstract class Handler {
private Handler next;
public void setNext(Handler next) {
this.next = next;
}
public Handler getNext() {
return next;
}
//處理請求的方法
public abstract void handleRequest(String request);
}
//具體處理者角色1
class ConcreteHandler1 extends Handler {
public void handleRequest(String request) {
if (request.equals("one")) {
System.out.println("具體處理者1負責處理該請求旺矾!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("沒有人處理該請求!");
}
}
}
}
//具體處理者角色2
class ConcreteHandler2 extends Handler {
public void handleRequest(String request) {
if (request.equals("two")) {
System.out.println("具體處理者2負責處理該請求夺克!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("沒有人處理該請求箕宙!");
}
}
}
}
輸出結(jié)果如下:
具體處理者2負責處理該請求!
解決的問題
將請求和處理分開懊直,請求者不用知道是誰處理的,處理者不用知道請求的全貌火鼻,實現(xiàn)了兩者之間的解耦室囊。
模式組成
組成(角色) | 作用 |
---|---|
抽象處理者(Handler)角色 | 定義一個處理請求的接口,包含抽象處理方法和一個后繼連接 |
具體處理者(Concrete Handler)角色 | 實現(xiàn)抽象處理者的處理方法魁索,判斷能否處理本次請求融撞,如果可以處理請求則處理,否則將該請求轉(zhuǎn)給它的后繼者粗蔚。 |
客戶類(Client)角色 | 創(chuàng)建處理鏈尝偎,并向鏈頭的具體處理者對象提交請求,它不關(guān)心處理細節(jié)和請求的傳遞過程鹏控。 |
實例說明
實例概況
用責任鏈模式設(shè)計一個請假條審批模塊致扯。
分析:假如規(guī)定學(xué)生請假小于或等于 2 天,班主任可以批準当辐;小于或等于 7 天抖僵,系主任可以批準;小于或等于 10 天缘揪,院長可以批準耍群;其他情況不予批準义桂;這個實例適合使用職責鏈模式實現(xiàn)。
首先蹈垢,定義一個領(lǐng)導(dǎo)類(Leader)慷吊,它是抽象處理者,包含了一個指向下一位領(lǐng)導(dǎo)的指針 next 和一個處理假條的抽象處理方法 handleRequest(int LeaveDays)曹抬;然后溉瓶,定義班主任類(ClassAdviser)、系主任類(DepartmentHead)和院長類(Dean)沐祷,它們是抽象處理者的子類嚷闭,是具體處理者,必須根據(jù)自己的權(quán)力去實現(xiàn)父類的 handleRequest(int LeaveDays) 方法赖临,如果無權(quán)處理就將假條交給下一位具體處理者胞锰,直到最后;客戶類負責創(chuàng)建處理鏈兢榨,并將假條交給鏈頭的具體處理者(班主任)嗅榕。
使用步驟
步驟1:定義抽象處理者(Handler)角色,抽象處理者:領(lǐng)導(dǎo)類
abstract class Leader {
private Leader next;
public void setNext(Leader next) {
this.next = next;
}
public Leader getNext() {
return next;
}
//處理請求的方法
public abstract void handleRequest(int LeaveDays);
}
步驟2:定義具體處理者(Concrete Handler)角色吵聪,具體處理者1:班主任類
class ClassAdviser extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 2) {
System.out.println("班主任批準您請假" + LeaveDays + "天凌那。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("請假天數(shù)太多,沒有人批準該假條吟逝!");
}
}
}
}
步驟3:定義具體處理者(Concrete Handler)角色帽蝶,具體處理者2:系主任類
class DepartmentHead extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 7) {
System.out.println("系主任批準您請假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("請假天數(shù)太多块攒,沒有人批準該假條励稳!");
}
}
}
}
步驟4:定義具體處理者(Concrete Handler)角色,具體處理者2:院長類
class Dean extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 10) {
System.out.println("院長批準您請假" + LeaveDays + "天囱井。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("請假天數(shù)太多驹尼,沒有人批準該假條!");
}
}
}
}
假如增加一個教務(wù)處長類庞呕,可以批準學(xué)生請假 20 天新翎,也非常簡單
class DeanOfStudies extends Leader {
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 20) {
System.out.println("教務(wù)處長批準您請假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("請假天數(shù)太多住练,沒有人批準該假條地啰!");
}
}
}
}
步驟5:測試
public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
//組裝責任鏈
Leader teacher1 = new ClassAdviser();
Leader teacher2 = new DepartmentHead();
Leader teacher3 = new Dean();
//Leader teacher4=new DeanOfStudies();
teacher1.setNext(teacher2);
teacher2.setNext(teacher3);
//teacher3.setNext(teacher4);
//提交請求
teacher1.handleRequest(8);
}
}
輸出結(jié)果
院長批準您請假8天。
優(yōu)點
- 降低了對象之間的耦合度讲逛。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鏈的結(jié)構(gòu)髓绽,發(fā)送者和接收者也無須擁有對方的明確信息。
- 增強了系統(tǒng)的可擴展性妆绞∷撑唬可以根據(jù)需要增加新的請求處理類枫攀,滿足開閉原則。
- 增強了給對象指派職責的靈活性株茶。當工作流程發(fā)生變化来涨,可以動態(tài)地改變鏈內(nèi)的成員或者調(diào)動它們的次序,也可動態(tài)地新增或者刪除責任启盛。
- 責任鏈簡化了對象之間的連接蹦掐。每個對象只需保持一個指向其后繼者的引用,不需保持其他所有處理者的引用僵闯,這避免了使用眾多的 if 或者 if···else 語句卧抗。
- 責任分擔。每個類只需要處理自己該處理的工作鳖粟,不該處理的傳遞給下一個對象完成社裆,明確各類的責任范圍,符合類的單一職責原則向图。
缺點
- 不能保證每個請求一定被處理泳秀。由于一個請求沒有明確的接收者,所以不能保證它一定會被處理榄攀,該請求可能一直傳到鏈的末端都得不到處理嗜傅。
- 對比較長的職責鏈,請求的處理可能涉及多個處理對象檩赢,系統(tǒng)性能將受到一定影響吕嘀。
- 職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的復(fù)雜性贞瞒,可能會由于職責鏈的錯誤設(shè)置而導(dǎo)致系統(tǒng)出錯偶房,如可能會造成循環(huán)調(diào)用。
應(yīng)用場景
- 有多個對象可以處理一個請求憔狞,哪個對象處理該請求由運行時刻自動確定蝴悉。
- 可動態(tài)指定一組對象處理請求彰阴,或添加新的處理者瘾敢。
- 在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求尿这。
模式的擴展
職責鏈模式存在以下兩種情況簇抵。
- 純的職責鏈模式:一個請求必須被某一個處理者對象所接收,且一個具體處理者對某個請求的處理只能采用以下兩種行為之一:自己處理(承擔責任)射众;把責任推給下家處理碟摆。
- 不純的職責鏈模式:允許出現(xiàn)某一個具體處理者對象在承擔了請求的一部分責任后又將剩余的責任傳給下家的情況,且一個請求可以最終不被任何接收端對象所接收叨橱。
源碼中的應(yīng)用
Filter
web.xml 配置filter
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
<servlet-name>dispatcher</servlet-name>
</filter-mapping>
filter有兩種過濾匹配方式典蜕,一種是url-pattern 一種是servlet-name 來匹配
Filter 概述
Servlet API中提供了一個Filter接口断盛,開發(fā)web應(yīng)用時,如果編寫的Java類實現(xiàn)了這個接口愉舔,則把這個java類稱之為過濾器Filter钢猛。
通過Filter技術(shù),開發(fā)人員可以實現(xiàn)用戶在訪問某個目標資源之前轩缤,對訪問的請求和響應(yīng)進行攔截命迈。簡單說,就是可以實現(xiàn)web容器對某資源的訪問前截獲進行相關(guān)的處理火的,還可以在某資源向web容器返回響應(yīng)前進行截獲進行處理壶愤。
Filter工作流程圖如下:[圖片上傳失敗...(image-354188-1603983809362)]
Tomcat中的Filter 是采用責任鏈設(shè)計模式 ,下面我們通過源碼分析下tomcat中Filter的實現(xiàn)
這里使用springboot 內(nèi)嵌tomcat以便于分析
肯定有人很好奇馏鹤,F(xiàn)ilter源碼入口是怎么找到的征椒。
我們可以借助idea的debug工具,查看調(diào)用方法的調(diào)用棧假瞬,這樣就可以知道想看的源碼是哪里開始的了
Filter源碼解析
入口為StandardWrapperValve.invoke()方法
StandardWrapperValve.invoke()方法
使用ApplictionFilterFactory創(chuàng)建FilterChain
ApplicationFilterChain
一個請求到達陕靠,生成ApplicationFilterChain對象,把servlet對象設(shè)置進去之后脱茉,添加匹配的filter(先根據(jù)url找剪芥,再根據(jù)name找,總共遍歷兩遍琴许,想不懂為啥要兩遍税肪,一次遍歷,不好嗎榜田?)
把要執(zhí)行的servlet存放到過濾器鏈中益兄。
如果沒有配置過濾器則return一個空的過濾器鏈(只包含上面設(shè)置的servlet)。
-
如果配置過濾器箭券,則把所有配置的過濾器加入到過濾器鏈中
首先判斷filter-mapping中配置的url-pattern規(guī)則净捅,如果符合則添加到過濾器鏈
然后判斷filter-mapping中配置的servlet-name規(guī)則,如果符合則添加到過濾器鏈
過濾器的順序是按照web.xml中filtermapping 的url-pattern匹配順序+ servlet-name匹配順序
繼續(xù)往下執(zhí)行StandardWrapperValve.invoke()方法辩块,往下執(zhí)行會執(zhí)行到filterChain.doFilter()
doFilter方法
可以看到執(zhí)行internalDoFilter()方法
調(diào)用internalDoFilter方法
ApplicationFilterChain#internalDoFilter()
pos:為過濾器鏈中當前執(zhí)行的過濾器下標蛔六。
n:過濾器鏈中的過濾器個數(shù)。
每執(zhí)行一個過濾器則把過濾器鏈中的post+1(下標)废亭,直到所有的過濾器的doFilter方法都調(diào)用成功国章。
filter.doFilter(request, response, this);
這行代碼是責任鏈設(shè)計模式的核心,把當前的過濾鏈傳入到doFilter方法中豆村。這樣在Filter中進行攔截液兽,通過過濾器判斷是否要進行調(diào)用下一個過濾器鏈。
如下:
如果不執(zhí)行FilterChain#doFilter()方法掌动,責任鏈就會終止
過濾器鏈中的所有過濾器的doFilter方法都執(zhí)行完成后四啰,最后再調(diào)用過濾器鏈中存放的servlet.service()方法宁玫。
PS:以上代碼提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git
文章持續(xù)更新,可以公眾號搜一搜「 一角錢技術(shù) 」第一時間閱讀柑晒, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄撬统,歡迎 Star。