一、概述
責(zé)任鏈模式(Chain of Responsibility Pattern)是將鏈中每一個(gè)節(jié)點(diǎn)看作是一個(gè)對(duì)象,每個(gè)節(jié)點(diǎn)處理的請(qǐng)求均不同,且內(nèi)部自動(dòng)維護(hù)一個(gè)下一節(jié)點(diǎn)對(duì)象。當(dāng)一個(gè)請(qǐng)求從鏈?zhǔn)降氖锥税l(fā)出時(shí),會(huì)沿著鏈的路徑依次傳遞給每一個(gè)節(jié)點(diǎn)對(duì)象,直至有對(duì)象處理這個(gè)請(qǐng)求為止,屬于行為型模式。就像一場(chǎng)足球比賽,通過層層傳遞,最終射門。
責(zé)任鏈模式的應(yīng)用場(chǎng)景
- 多個(gè)對(duì)象可以處理一個(gè)請(qǐng)求,但具體由哪個(gè)對(duì)象處理該請(qǐng)求在運(yùn)行時(shí)自動(dòng)確定。
- 可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求,或添加新的處理者。
- 需要在不明確指定請(qǐng)求處理者的情況下,向多個(gè)處理者中的一個(gè)提交請(qǐng)求。
設(shè)計(jì)模式只是幫助減少代碼的復(fù)雜性,讓其滿足開閉原則,提高代碼的擴(kuò)展性。如果不使用同樣可以完成需求。
假設(shè)業(yè)務(wù)場(chǎng)景是這樣的,我們 系統(tǒng)處在一個(gè)下游服務(wù),因?yàn)闃I(yè)務(wù)需求,系統(tǒng)中所使用的 基礎(chǔ)數(shù)據(jù)需要從上游中臺(tái)同步到系統(tǒng)數(shù)據(jù)庫
基礎(chǔ)數(shù)據(jù)包含了很多類型數(shù)據(jù),雖然數(shù)據(jù)在中臺(tái)會(huì)有一定驗(yàn)證,但是 數(shù)據(jù)只要是人為錄入就極可能存在問題,遵從對(duì)上游系統(tǒng)不信任原則,需要對(duì)數(shù)據(jù)接收時(shí)進(jìn)行一系列校驗(yàn)
最初是要進(jìn)行一系列驗(yàn)證原則才能入庫的,后來因?yàn)楣て趩栴}只放了一套非空驗(yàn)證,趁著春節(jié)期間時(shí)間還算寬裕,把這套驗(yàn)證規(guī)則骨架放進(jìn)去
從我們系統(tǒng)的接入數(shù)據(jù)規(guī)則而言,個(gè)人覺得需要支持以下幾套規(guī)則
- 必填項(xiàng)校驗(yàn),如果數(shù)據(jù)無法滿足業(yè)務(wù)所必須字段要求,數(shù)據(jù)一旦落入庫中就會(huì)產(chǎn)生一系列問題
- 非法字符校驗(yàn),因?yàn)閿?shù)據(jù)如何錄入,上游系統(tǒng)的錄入規(guī)則是什么樣的我們都不清楚,這一項(xiàng)規(guī)則也是必須的
- 長(zhǎng)度校驗(yàn),理由同上,如果系統(tǒng)某字段長(zhǎng)度限制 50,但是接入來的數(shù)據(jù) 500長(zhǎng)度,這也會(huì)造成問題
如果不使用責(zé)任鏈模式,上面說的真實(shí)同步場(chǎng)景面臨兩個(gè)問題
- 如果把上述說的代碼邏輯校驗(yàn)規(guī)則寫到一起,毫無疑問這個(gè)類或者說這個(gè)方法函數(shù)奇大無比。減少代碼復(fù)雜性一貫方法是:將大塊代碼邏輯拆分成函數(shù),將大類拆分成小類,是應(yīng)對(duì)代碼復(fù)雜性的常用方法。如果此時(shí)說:可以把不同的校驗(yàn)規(guī)則拆分成不同的函數(shù),不同的類,這樣不也可以滿足減少代碼復(fù)雜性的要求么。這樣拆分是能解決代碼復(fù)雜性,但是這樣就會(huì)面臨第二個(gè)問題
- 開閉原則:添加一個(gè)新的功能應(yīng)該是,在已有代碼基礎(chǔ)上擴(kuò)展代碼,而非修改已有代碼。大家設(shè)想一下,假設(shè)你寫了三套校驗(yàn)規(guī)則,運(yùn)行過一段時(shí)間,這時(shí)候領(lǐng)導(dǎo)讓加第四套,是不是要在原有代碼上改動(dòng)
綜上所述,在合適的場(chǎng)景運(yùn)用適合的設(shè)計(jì)模式,能夠讓代碼設(shè)計(jì)復(fù)雜性降低,變得更為健壯。朝更遠(yuǎn)的說也能讓自己的編碼設(shè)計(jì)能力有所提高。
優(yōu)點(diǎn)
- 將請(qǐng)求與處理解耦。
- 請(qǐng)求處理者(節(jié)點(diǎn)對(duì)象)只需要關(guān)注自己感興趣的請(qǐng)求進(jìn)行處理即可,對(duì)于不感興趣的請(qǐng)求,轉(zhuǎn)發(fā)給下一個(gè)節(jié)點(diǎn)。
- 具備鏈?zhǔn)絺鬟f處理請(qǐng)求功能,請(qǐng)求發(fā)送者無需知曉鏈路結(jié)構(gòu),只需等待請(qǐng)求處理結(jié)果。
- 鏈路結(jié)構(gòu)靈活,可以通過改變鏈路的結(jié)構(gòu)動(dòng)態(tài)的新增或刪減責(zé)任。
- 易于擴(kuò)展新的請(qǐng)求處理類(節(jié)點(diǎn)),符合開閉原則。
缺點(diǎn)
- 責(zé)任鏈太長(zhǎng)或者處理時(shí)間過長(zhǎng),會(huì)影響整體性能。
- 如果節(jié)點(diǎn)對(duì)象存在循環(huán)引用時(shí),會(huì)造成死循環(huán),導(dǎo)致系統(tǒng)崩潰。
二、入門案例
2.1 類圖

2.2 基礎(chǔ)類介紹
抽象接口RequestHandler
public interface RequestHandler {
void doHandler(String req);
}
抽象類BaseRequestHandler
public abstract class BaseRequestHandler implements RequestHandler {
protected RequestHandler next;
public void next(RequestHandler next) {
this.next = next;
}
}
具體處理類AHandler
public class AHandler extends BaseRequestHandler {
@Override
public void doHandler(String req) {
// 處理自己的業(yè)務(wù)邏輯
System.out.println("A中處理自己的邏輯");
// 傳遞給下個(gè)類(若鏈路中還有下個(gè)處理類)
if (next != null) {
next.doHandler(req);
}
}
}
當(dāng)然還有具體的處理類B、C等等,這里不展開贅述。
使用類Client
public class Client {
public static void main(String[] args) {
BaseRequestHandler a = new AHandler();
BaseRequestHandler b = new BHandler();
BaseRequestHandler c = new CHandler();
a.next(b);
b.next(c);
a.doHandler("鏈路待處理的數(shù)據(jù)");
}
}
2.3 處理流程圖
三、應(yīng)用場(chǎng)景
3.1 場(chǎng)景舉例
場(chǎng)景一
金融業(yè)務(wù)其中就有一個(gè)業(yè)務(wù)場(chǎng)景:一筆訂單進(jìn)來,會(huì)先在后臺(tái)通過初審人員進(jìn)行審批,初審不通過,訂單流程結(jié)束。初審?fù)ㄟ^以后,會(huì)轉(zhuǎn)給終審人員進(jìn)行審批,不通過,流程結(jié)束;通過,流轉(zhuǎn)到下個(gè)業(yè)務(wù)場(chǎng)景。
對(duì)于這塊業(yè)務(wù)代碼,一套if-else干到底。后來,技術(shù)老大CodeReview,點(diǎn)名要求改掉這塊。(當(dāng)然,比較復(fù)雜的情況,還是可以用工作流來處理這個(gè)場(chǎng)景)。
場(chǎng)景二
有的公司業(yè)務(wù)會(huì)調(diào)用我們接口,將數(shù)據(jù)同步過來。同樣,我們需要將處理好的數(shù)據(jù),傳給他們。由于雙方傳輸數(shù)據(jù)都是加密傳輸,所以在接受他們數(shù)據(jù)之前,需要對(duì)數(shù)據(jù)進(jìn)行解密,驗(yàn)簽,參數(shù)校驗(yàn)等操作。同樣,我們給他們傳數(shù)據(jù)也需要進(jìn)行加簽,加密操作。
具體案例
對(duì)于場(chǎng)景二,我們結(jié)合代碼一起探討一下。
1、一切從注解開始,我這里自定義了一個(gè)注解@Duty,這個(gè)注解有spring的@Component注解,也就是標(biāo)記了這個(gè)自定義注解的類,都是交給spring的bean容器去管理。
注解中,有兩個(gè)屬性:1.type,定義相同的type類型的bean,會(huì)被放到一個(gè)責(zé)任鏈集合中。2.order,同一個(gè)責(zé)任鏈集合中,bean的排序,數(shù)值越小,會(huì)放到鏈路最先的位置,優(yōu)先處理。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
public @interface Duty {
/**
* 標(biāo)記具體業(yè)務(wù)場(chǎng)景
* @return
*/
String type() default "";
/**
* 排序:數(shù)值越小,排序越前
* @return
*/
int order() default 0;
}
2、定義一個(gè)頂層的抽象接口IHandler,傳入2個(gè)泛型參數(shù),供后續(xù)自定義。
public interface IHandler<T, R> {
/**
* 抽象處理類
* @param t
* @return
*/
R handle(T t);
}
3、定義一個(gè)責(zé)任鏈bean的管理類HandleChainManager,用來存放不同業(yè)務(wù)下的責(zé)任鏈路集合。在該類中,有一個(gè)Map和兩個(gè)方法。
- handleMap:這個(gè)map會(huì)存放責(zé)任鏈路中,具體的執(zhí)行類,key是注解
@Duty中定義的type值,value是標(biāo)記了@Duty注解的bean集合,也就是具體的執(zhí)行類集合。 - setHandleMap:傳入具體執(zhí)行bean的集合,存放在map中。
- executeHandle:從map中找到具體的執(zhí)行bean集合,并依次執(zhí)行。
public class HandleChainManager {
/**
* 存放責(zé)任鏈路上的具體處理類
* k-具體業(yè)務(wù)場(chǎng)景名稱
* v-具體業(yè)務(wù)場(chǎng)景下的責(zé)任鏈路集合
*/
private Map<String, List<IHandler>> handleMap;
/**
* 存放系統(tǒng)中責(zé)任鏈具體處理類
* @param handlerList
*/
public void setHandleMap(List<IHandler> handlerList) {
handleMap = handlerList
.stream()
.sorted(Comparator.comparingInt(h -> AnnotationUtils.findAnnotation(h.getClass(), Duty.class).order()))
.collect(Collectors.groupingBy(handler -> AnnotationUtils.findAnnotation(handler.getClass(), Duty.class).type()));
}
/**
* 執(zhí)行具體業(yè)務(wù)場(chǎng)景中的責(zé)任鏈集合
* @param type 對(duì)應(yīng)@Duty注解中的type,可以定義為具體業(yè)務(wù)場(chǎng)景
* @param t 被執(zhí)行的參數(shù)
*/
public <T, R> R executeHandle(String type, T t) {
List<IHandler> handlers = handleMap.get(type);
R r = null;
if (CollectionUtil.isNotEmpty(handlers)) {
for (IHandler<T, R> handler : handlers) {
r = handler.handle(t);
}
}
return r;
}
}
4、定義一個(gè)配置類PatternConfiguration,用于裝配上面的責(zé)任鏈管理器HandleChainManager。
@Configuration
public class PatternConfiguration {
@Bean
public HandleChainManager handlerChainExecute(List<IHandler> handlers) {
HandleChainManager handleChainManager = new HandleChainManager();
handleChainManager.setHandleMap(handlers);
return handleChainManager;
}
}
5、具體的處理類:SignChainHandler、EncryptionChainHandler、RequestChainHandler,這里我以SignChainHandler為例。
在具體處理類上標(biāo)記自定義注解@Duty,該類會(huì)被注入到bean容器中,實(shí)現(xiàn)IHandler接口,只需關(guān)心自己的handle方法,處理具體的業(yè)務(wù)邏輯。
@Duty(type = BusinessConstants.REQUEST, order = 1)
public class SignChainHandler implements IHandler<String, String> {
/**
* 處理加簽邏輯
* @param s
* @return
*/
@Override
public String handle(String s) {
// 加簽邏輯
System.out.println("甲方爸爸要求加簽");
return "加簽";
}
}
6、具體怎么調(diào)用?這里我寫了個(gè)測(cè)試controller直接調(diào)用,具體如下:
@RestController
@Slf4j
public class TestController {
@Resource
private HandleChainManager handleChainManager;
@PostMapping("/send")
public String duty(@RequestBody String requestBody) {
String response = handleChainManager.executeHandle(BusinessConstants.REQUEST, requestBody);
return response;
}
}
7、執(zhí)行結(jié)果,會(huì)按照注解中標(biāo)記的order依次執(zhí)行。

至此,完工。又可以開心的擼代碼了,然后在具體的執(zhí)行類中,又是一頓if-else。。。
四、源碼中運(yùn)用
4.1Mybatis源碼中的運(yùn)用
Mybatis中的緩存接口Cache,cache作為一個(gè)緩存接口,最主要的功能就是添加和獲取緩存的功能,作為接口它有11個(gè)實(shí)現(xiàn)類,分別實(shí)現(xiàn)不同的功能,下面是接口源碼和實(shí)現(xiàn)類。
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
String getId();
void putObject(Object var1, Object var2);
Object getObject(Object var1);
Object removeObject(Object var1);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}

下面,我們來看下其中一個(gè)子類LoggingCache的源碼。主要看他的putObject方法和getObject方法,它在方法中直接傳給下一個(gè)實(shí)現(xiàn)去執(zhí)行。這個(gè)實(shí)現(xiàn)類其實(shí)是為了在獲取緩存的時(shí)候打印緩存的命中率的。
public class LoggingCache implements Cache {
private final Log log;
private final Cache delegate;
protected int requests = 0;
protected int hits = 0;
public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(this.getId());
}
// ...
public void putObject(Object key, Object object) {
this.delegate.putObject(key, object);
}
public Object getObject(Object key) {
++this.requests;
Object value = this.delegate.getObject(key);
if (value != null) {
++this.hits;
}
if (this.log.isDebugEnabled()) {
this.log.debug("Cache Hit Ratio [" + this.getId() + "]: " + this.getHitRatio());
}
return value;
}
// ...
}
最后,經(jīng)過Cache接口各種實(shí)現(xiàn)類的處理,最終會(huì)到達(dá)PerpetualCache這個(gè)實(shí)現(xiàn)類。與之前的處理類不同的是,這個(gè)類中有一個(gè)map,在map中做存取,也就是說,最終緩存還是會(huì)保存在map中的。
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap();
public PerpetualCache(String id) {
this.id = id;
}
// ...
public void putObject(Object key, Object value) {
this.cache.put(key, value);
}
public Object getObject(Object key) {
return this.cache.get(key);
}
// ...
}
4.2spring源碼中的運(yùn)用
4.2.1DispatcherServlet類
DispatcherServlet 核心方法 doDispatch。HandlerExecutionChain只是維護(hù)HandlerInterceptor的集合,可以向其中注冊(cè)相應(yīng)的攔截器,本身不直接處理請(qǐng)求,將請(qǐng)求分配給責(zé)任鏈上注冊(cè)處理器執(zhí)行,降低職責(zé)鏈本身與處理邏輯之間的耦合程度。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
4.2.2HandlerExecutionChain類
這里分析的幾個(gè)方法,都是從DispatcherServlet類的doDispatch方法中請(qǐng)求的。
- 獲取攔截器,執(zhí)行preHandle方法
boolean applyPreHandle(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
- 在applyPreHandle方法中,執(zhí)行triggerAfterCompletion方法
void triggerAfterCompletion(HttpServletRequest request,
HttpServletResponse response, Exception ex) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for(int i = this.interceptorIndex; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable var8) {
logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
}
}
}
}
- 獲取攔截器,執(zhí)行applyPostHandle方法
void applyPostHandle(HttpServletRequest request,
HttpServletResponse response, ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
五、代碼示例
員工在OA系統(tǒng)中提交請(qǐng)假申請(qǐng),首先項(xiàng)目經(jīng)理處理,他能審批3天以內(nèi)的假期,如果大于3天,則由項(xiàng)目經(jīng)理則轉(zhuǎn)交給總經(jīng)理處理。接下來我們用責(zé)任鏈模式實(shí)現(xiàn)這個(gè)過程。
1、封裝請(qǐng)假信息實(shí)體類
public class LeaveRequest {
private String name; // 請(qǐng)假人姓名
private int numOfDays; // 請(qǐng)假天數(shù)
private int workingAge; //員工工齡(在公司大于2年則總經(jīng)理會(huì)審批)
//省略get..set..
}
2、抽象處理者類 Handler,維護(hù)一個(gè)nextHandler屬性,該屬性為當(dāng)前處理者的下一個(gè)處理者的引用;
聲明了抽象方法process,其實(shí)在這里也用了方法模板模式:
public abstract class ApproveHandler {
protected ApproveHandler nextHandler;//下一個(gè)處理者(與類一致,這段代碼很重要)
public void setNextHandler(ApproveHandler approveHandler){
this.nextHandler=approveHandler;
}
public abstract void process(LeaveRequest leaveRequest); // 處理請(qǐng)假(這里用了模板方法模式)
}
3、項(xiàng)目經(jīng)理處理者,能處理小于3天的假期,而請(qǐng)假信息里沒有名字時(shí),審批不通過:
public class PMHandler extends ApproveHandler{
@Override
public void process(LeaveRequest leaveRequest) {
//未填寫姓名的請(qǐng)假單不通過
if(null != leaveRequest.getName()){
if(leaveRequest.getNumOfDays() <= 3){
System.out.println(leaveRequest.getName()+",你通過項(xiàng)目經(jīng)理審批!");
}else {
System.out.println("項(xiàng)目經(jīng)理轉(zhuǎn)交總經(jīng)理");
if(null != nextHandler){
nextHandler.process(leaveRequest);
}
}
}else {
System.out.println("請(qǐng)假單未填寫完整,未通過項(xiàng)目經(jīng)理審批!");
return;
}
}
}
4、總經(jīng)理處理者,能處理大于3天的假期,且工齡超過2年才會(huì)審批通過:
public class GMHandler extends ApproveHandler{
@Override
public void process(LeaveRequest leaveRequest) {
//員工在公司工齡超過2年,則審批通過
if(leaveRequest.getWorkingAge() >=2 && leaveRequest.getNumOfDays() > 3){
System.out.println(leaveRequest.getName()+",你通過總經(jīng)理審批!");
if(null != nextHandler){
nextHandler.process(leaveRequest);
}
}else {
System.out.println("在公司年限不夠,長(zhǎng)假未通過總經(jīng)理審批!");
return;
}
}
}
實(shí)例代碼完成,我們測(cè)試一下:
public class Test {
public static void main(String[] args) {
PMHandler pm = new PMHandler();
GMHandler gm = new GMHandler();
LeaveRequest leaveRequest = new LeaveRequest();
leaveRequest.setName("張三");
leaveRequest.setNumOfDays(4);//請(qǐng)假4天
leaveRequest.setWorkingAge(3);//工齡3年
pm.setNextHandler(gm);//設(shè)置傳遞順序
pm.process(leaveRequest);
}
}
運(yùn)行結(jié)果:
項(xiàng)目經(jīng)理轉(zhuǎn)交總經(jīng)理
張三,你通過總經(jīng)理審批!
六、源碼中的典型應(yīng)用
源碼中的典型應(yīng)用:
- Netty 中的 Pipeline和ChannelHandler通過責(zé)任鏈設(shè)計(jì)模式來組織代碼邏輯。
- Spring Security 使用責(zé)任鏈模式,可以動(dòng)態(tài)地添加或刪除責(zé)任(處理 request 請(qǐng)求)。
- Spring AOP 通過責(zé)任鏈模式來管理 Advisor。
- Dubbo Filter 過濾器鏈也是用了責(zé)任鏈模式(鏈表),可以對(duì)方法調(diào)用做一些過濾處理,譬如超時(shí)(TimeoutFilter),異常(ExceptionFilter),Token(TokenFilter)等。
- Mybatis 中的 Plugin 機(jī)制使用了責(zé)任鏈模式,配置各種官方或者自定義的 Plugin,與 Filter 類似,可以在執(zhí)行 Sql 語句的時(shí)候做一些操作。
- Tomcat 調(diào)用 ApplicationFilterFactory過濾器鏈。
spring安全框架security使用責(zé)任鏈模式
spring安全框架security使用責(zé)任鏈模式,框架使用者可以動(dòng)態(tài)地添加刪除責(zé)任(處理request請(qǐng)求)。
UML 類圖

活動(dòng)圖:

源碼解析:currentPosition表示責(zé)任鏈的要處理請(qǐng)求鏈條節(jié)點(diǎn)的位置,使用additionalFilters來依次處理request請(qǐng)求。additionalFilters中的每個(gè)Filter成員都承擔(dān)某一項(xiàng)具體職責(zé),并且每個(gè)Filter都會(huì)被執(zhí)行到。 責(zé)任鏈條的成員執(zhí)行完自己的職責(zé)后,會(huì)回調(diào)鏈條的處理請(qǐng)求方法,責(zé)任鏈條會(huì)找到下一個(gè)鏈條成員來執(zhí)行職責(zé),直到鏈條尾端。
private static class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain; //鏈條中的節(jié)點(diǎn)全部執(zhí)行完后,處理request請(qǐng)求的對(duì)象
private final List<Filter> additionalFilters; //請(qǐng)求實(shí)際執(zhí)行者,
private final FirewalledRequest firewalledRequest;
private final int size;
private int currentPosition = 0; //鏈條移動(dòng)的位置,當(dāng)currentPosition==size,到達(dá)鏈條的尾端。
private VirtualFilterChain(FirewalledRequest firewalledRequest,
FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
this.firewalledRequest = firewalledRequest;
}
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == size) { //到達(dá)鏈條尾端
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
originalChain.doFilter(request, response);
}
else {
currentPosition++; //依次移動(dòng)鏈條指針到具體節(jié)點(diǎn)
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
nextFilter.doFilter(request, response, this);//將鏈條本身的對(duì)象傳遞給鏈條成員
}
}
}
鏈條成員Filter會(huì)執(zhí)行chain.doFilter(request, response )方法,而chain是鏈條本身的引用,這樣成員就將請(qǐng)求又重新交給了鏈條??碨ecurityContextHolderAwareRequestFilter源碼:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
(HttpServletResponse) res), res);
}
七:設(shè)計(jì)模式重語意
最后說一下需要達(dá)成的業(yè)務(wù)需求。將一個(gè)批量數(shù)據(jù)經(jīng)過處理器鏈的處理,返回出符合要求的數(shù)據(jù)分類

定義頂級(jí)驗(yàn)證接口和一系列處理器實(shí)現(xiàn)類沒什么難度,但是應(yīng)該如何進(jìn)行鏈?zhǔn)秸{(diào)用呢?
這一塊代碼需要有一定 Spring 基礎(chǔ)才能理解,一起來看下 VerifyHandlerChain 如何將所有處理器串成一條鏈

VerifyHandlerChain 處理流程如下:
- 實(shí)現(xiàn)自 InitializingBean 接口,在對(duì)應(yīng)實(shí)現(xiàn)方法中獲取 IOC 容器中類型為 VerifyHandler 的 Bean,也就是 EmptyVerifyHandler、SexyVerifyHandler
- 將 VerifyHandler 類型的 Bean 添加到處理器鏈容器中
- 定義校驗(yàn)方法 verify(),對(duì)入?yún)?shù)據(jù)展開處理器鏈的全部調(diào)用,如果過程中發(fā)現(xiàn)已無需要驗(yàn)證的數(shù)據(jù),直接返回
這里使用 SpringBoot 項(xiàng)目中默認(rèn)測(cè)試類,來測(cè)試一下如何調(diào)用
@SpringBootTest
class ChainApplicationTests {
@Autowired
private VerifyHandlerChain verifyHandlerChain;
@Test
void contextLoads() {
List<Object> verify = verifyHandlerChain.verify(Lists.newArrayList("源碼圈", "@一只阿木木"));
System.out.println(verify);
}
}
這樣的話,如果客戶或者產(chǎn)品提校驗(yàn)相關(guān)的需求時(shí),我們只需要實(shí)現(xiàn) VerifyHandler 接口新建個(gè)校驗(yàn)規(guī)則實(shí)現(xiàn)類就 OK 了,這樣符合了設(shè)計(jì)模式的原則:滿足開閉原則,提高代碼的擴(kuò)展性
熟悉之前作者寫過設(shè)計(jì)模式的文章應(yīng)該知道,強(qiáng)調(diào)設(shè)計(jì)模式重語意,而不是具體的實(shí)現(xiàn)過程。所以,你看這個(gè)咱們這個(gè)校驗(yàn)代碼,把責(zé)任鏈兩種模式結(jié)合了使用