定義
使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免了請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系。將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有對(duì)象處理它為止
使用場(chǎng)景
如果一個(gè)請(qǐng)求可能會(huì)出現(xiàn)多個(gè)或未知個(gè)數(shù)處理器實(shí)例,或者請(qǐng)求處理器可動(dòng)態(tài)配置的情況下,這時(shí)候便可使用責(zé)任鏈模式。
幾乎所有的開(kāi)源框架中都使用到該模式,如 Spring 中的攔截器、過(guò)濾器,通過(guò) ant 表達(dá)式風(fēng)格的 url 參數(shù)來(lái)判斷是否對(duì)請(qǐng)求進(jìn)行攔截,Netty 中 ChannelPipeLine 通過(guò)鏈表的形式添加 ChannelHandler 處理節(jié)點(diǎn)。
在SpringBoot中使用
結(jié)構(gòu)圖

責(zé)任鏈的處理核心在"鏈"(Chain)上面。"鏈"是由多個(gè)處理者 ConcreateX 組成的,我們先來(lái)看抽象 “Handler” 接口:
/**
* @author tianp
**/
public interface Handler {
/**
* 是否支持處理
*
* @param uri 匹配uri
* @return true 成功 false 失敗
*/
boolean support(String uri);
/**
* 處理
*
* @param requestBody 處理參數(shù)封裝
* @return true 成功 false 失敗
*/
boolean handle(RequestBody requestBody);
/**
* 獲取處理器名字
*
* @return 處理器名字
*/
String getName();
}
RequestBody 對(duì)請(qǐng)求的參數(shù)進(jìn)行封裝
/**
* 請(qǐng)求處理封裝類
*
* @author tianp
**/
public class RequestBody {
private String uri;
//根據(jù)業(yè)務(wù)定義
private Object body;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public Object getBody() {
return body;
}
public void setBody(Object body) {
this.body = body;
}
}
處理器接口 “Hanlder” 定義了三個(gè)標(biāo)準(zhǔn):
- 是否支持處理。通過(guò)參數(shù) uri 判斷當(dāng)前 “Handler” 是否支持處理, 如果當(dāng)前不能處理就傳遞給下一個(gè) “Handler” 處理
- 真正進(jìn)行處理的方法。傳遞 RequestBody給 handle 方法,處理成功則傳遞下一個(gè),不成功則返回false,結(jié)束。
- 獲取當(dāng)前處理器名字。便于打印日志排查問(wèn)題
/**
* @author tianp
**/
public abstract class AbstractHandler implements Handler {
/**
* 處理器名稱
*/
private String name;
/**
* 攔截uri
*/
private String[] includePatterns;
/**
* 放行uri
*/
private String[] excludePatterns;
/**
* ant 匹配
*/
private PathMatcher pathMatcher = new AntPathMatcher();
/**
* 下一個(gè)處理器
*/
private AbstractHandler next = null;
public AbstractHandler(String name) {
this.name = name;
}
public boolean support(String uri) {
if (excludePatterns != null) {
for (String exclude : excludePatterns) {
if (pathMatcher.match(exclude, uri)) {
return false;
}
}
}
if (includePatterns != null) {
for (String include : includePatterns) {
if (pathMatcher.match(include, uri)) {
return true;
}
}
}
return false;
}
/**
* 給子類實(shí)現(xiàn)的真正處理方法
*
* @param requestBody 請(qǐng)求參數(shù)
* @return true 成功 false 失敗
*/
public abstract boolean process(RequestBody requestBody);
public boolean handle(RequestBody requestBody) {
if (next != null) {
if (next.support(requestBody.getUri())) {
System.out.println(next.getName() + "開(kāi)始處理");
return next.process(requestBody);
} else {
next.handle(requestBody);
}
}
return true;
}
}
//.... 省略 get/set 方法
抽象處理器 AbstractHandler 實(shí)現(xiàn)了 Handler 接口,實(shí)現(xiàn)了對(duì)應(yīng)的
- support() 通過(guò) AntPathMatcher 使用 ant 風(fēng)格的表達(dá)式來(lái)匹配當(dāng)前類是否支持?jǐn)r截
- handle() 通過(guò)持有一個(gè) next 指針來(lái)進(jìn)行請(qǐng)求傳遞
- getName()
- process() 給子類實(shí)現(xiàn)的真正處理的方法
/**
* @author tianp
**/
public class LinkedHandlerChain {
/**
* 頭節(jié)點(diǎn)
*/
private static final AbstractHandler HEAD = new AbstractHandler("head"){
@Override
public boolean process(RequestBody requestBody) {
return true;
}
};
private AbstractHandler TAIL = HEAD;
public void addLast(AbstractHandler handler){
TAIL.setNext(handler);
TAIL = handler;
}
public boolean handle(RequestBody requestBody){
return HEAD.handle(requestBody);
}
}
最后通過(guò)一個(gè)鏈 LinkedhandlerChain 串起來(lái)。通過(guò)持有鏈表的指針 HEAD、TAIL 來(lái)處理和添加節(jié)點(diǎn)。
優(yōu)點(diǎn)
非常顯著的優(yōu)點(diǎn)就是將請(qǐng)求和處理分開(kāi)。請(qǐng)求者不用知道是誰(shuí)處理的,處理者不用知道請(qǐng)求的全貌。兩者解耦,提高系統(tǒng)靈活性
缺點(diǎn)
- 性能問(wèn)題。每個(gè)請(qǐng)求都是從鏈頭遍歷到鏈尾,特別是在鏈比較長(zhǎng)的時(shí)候,性能是一個(gè)非常大的問(wèn)題。
- 調(diào)試不方便。當(dāng)鏈比較長(zhǎng)、環(huán)節(jié)比較多的時(shí)候,由于采取了類似遞歸的方式,調(diào)試的時(shí)候邏輯可能比較復(fù)雜
最佳實(shí)踐
責(zé)任鏈在實(shí)際項(xiàng)目中,曾經(jīng)在項(xiàng)目中使用它來(lái)對(duì)一部分功能進(jìn)行鑒權(quán),因?yàn)楫?dāng)時(shí)只有一部分接口需要鑒權(quán),如果引入第三方框架如:Spring Security 和 Shiro 不僅增加項(xiàng)目的復(fù)雜度,還讓項(xiàng)目變 “重”,因此通過(guò)引入 責(zé)任鏈模式,我可以很好的解決這個(gè)問(wèn)題
更多參考
代碼倉(cāng)庫(kù)地址:https://github.com/To-echo/chain-design/tree/master
參考書(shū)籍:《設(shè)計(jì)模式之禪》