責任鏈模式詳解

1.責任鏈模式概述

責任鏈模式,顧名思義,就是用來處理相關業務邏輯的一條執行鏈,執行鏈上有多個節點,每個節點都有機會(條件匹配)處理請求,如果某個節點處理完了就可以根據實際業務需求傳遞給下一個節點繼續處理或者返回處理完畢。責任鏈模式按照整個執行鏈中節點的組織形式,可以分爲兩種:一種是“鏈表模式”,另一種是“數組模式”。“鏈表模式”類似於數據結構中的鏈表數據結構,請求者只知道鏈表的root節點,對鏈表上的其他節點一無所知。請求發起後,會將控制權傳遞給root節點,root節點保存了指向鏈條下一節點的指針,由root節點傳遞給下一節點,依次類推,下一節點處理完成後,再將控制權傳遞給下下一節點,直到鏈條所有節點處理完成,控制權再返回給請求者。如下圖所示:
責任鏈模式示例圖
“數組模式”,類似於數據結構中的數組數據結構,由請求者維護一個節點數組,並按照數據下標的順序依次執行,節點的鏈條上的節點不知道其他節點的存在。
如下圖所示:
在這裏插入圖片描述
在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。

2. 適用範圍

責任鏈模式主要用於系統公共調度,能夠滿足調度節點間的松耦合,實現動態添加或刪除調度節點。“鏈表模式”:適用於責任鏈是個黑盒的方式,責任鏈具有自維護特性。當責任鏈模塊是由另一個團隊開發時,爲了儘可能減少主程序與責任鏈調度模塊的關聯,主控程序只知道責任鏈的入口節點,後續鏈條流轉,主程序不再關注的場景。“數組模式”:適用於責任鏈調度由主程序來控制的場景,這種模式責任鏈節點間互相不知道,減少了節點間的耦合。

3.使用SpringMVC 攔截器源碼分析

3.1 SpringMVC前端控制器流程

在介紹SpringMVC攔截器之前,有必要先介紹一下攔截器在SpringMVC框架調度流程中的位置。

在這裏插入圖片描述

具體步驟: 
 第一步:發起請求到前端控制器(DispatcherServlet) 
 第二步:前端控制器請求HandlerMapping查找 Handler (可以根據xml配置、註解進行查找)  
 第三步:處理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping會把請求映射爲HandlerExecutionChain對象      (包含一個Handler處理器(頁面控制器)對象,多個HandlerInterceptor攔截器對象),通過這種策略模式,很容易      添加新的映射策略  
 第四步:前端控制器調用處理器適配器去執行Handler  
 第五步:處理器適配器HandlerAdapter將會根據適配的結果去執行Handler  
 第六步:Handler執行完成給適配器返回ModelAndView 
 第七步:處理器適配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一個底層對象,包括 Model和view)
 第八步:前端控制器請求視圖解析器去進行視圖解析 (根據邏輯視圖名解析成真正的視圖(jsp)),通過這種策略很容易更換其      他視圖技術,只需要更改視圖解析器即可  
 第九步:視圖解析器向前端控制器返回View
 第十步:前端控制器進行視圖渲染 (視圖渲染將模型數據(在ModelAndView對象中)填充到request域)  
 第十一步:前端控制器向用戶響應結果。

3.2什麼是SpringMVC攔截器

SpringMVC攔截器就是用於在覈心業務處理器即 Handler處理之前與處理之後,做一些特殊處理。如下圖所示是最簡單的情況,即Handler執行前有一個特殊處理,Handler執行後有一個特殊處理:

在這裏插入圖片描述
如果在覈心業務處理邏輯之前或之後,有很多相互之間不關聯的前處理或後處理,我們當然可以全部寫在前處理器上,也可以全部寫到後處理器上,但這樣業務不相關的代碼就耦合到一個類或函數中了,不符合“高內聚、低耦合”的設計原則,怎麼辦呢?定義多個處理器不就行了,每個處理器完成各自的特殊處理就行了。如下圖所示:

在這裏插入圖片描述
這時候又遇到一個新問題,SpringMVC是一個開源框架,提供給開發者的是jar的形式,如果開發者想在前處理器或後處理器之間添加自己的特殊處理器,難道需要修改SpringMVC源碼,添加自己的前處理器或後處理器類嗎?這樣就違背了面向對象的“開-閉”原則,SpringMVC的設計者當然不會這麼笨。這時候責任鏈模式就起到了作用。

3.3源碼分析

以下源碼是SpringMVC的核心控制器DispatcherServlet的核心處理邏輯:

1.protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  
2.        HttpServletRequest processedRequest = request;  
3.        HandlerExecutionChain mappedHandler = null;  
4.        boolean multipartRequestParsed = false;  
5.  
6.        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);  
7.  
8.        try {  
9.            ModelAndView mv = null;  
10.            Exception dispatchException = null;  
11.  
12.            try {  
13.                processedRequest = checkMultipart(request);  
14.                multipartRequestParsed = (processedRequest != request);  
15.  
16.                // Determine handler for the current request.  
17.                mappedHandler = getHandler(processedRequest);  
18.                if (mappedHandler == null || mappedHandler.getHandler() == null) {  
19.                    noHandlerFound(processedRequest, response);  
20.                    return;  
21.                }  
22.  
23.                // Determine handler adapter for the current request.  
24.                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
25.  
26.                // Process last-modified header, if supported by the handler.  
27.                String method = request.getMethod();  
28.                boolean isGet = "GET".equals(method);  
29.                if (isGet || "HEAD".equals(method)) {  
30.                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());  
31.                    if (logger.isDebugEnabled()) {  
32.                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);  
33.                    }  
34.                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {  
35.                        return;  
36.                    }  
37.                }  
38.  
39.                if (!mappedHandler.applyPreHandle(processedRequest, response)) {  
40.                    return;  
41.                }  
42.  
43.                // Actually invoke the handler.  
44.                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
45.  
46.                if (asyncManager.isConcurrentHandlingStarted()) {  
47.                    return;  
48.                }  
49.  
50.                applyDefaultViewName(processedRequest, mv);  
51.                mappedHandler.applyPostHandle(processedRequest, response, mv);  
52.            }  
53.            catch (Exception ex) {  
54.                dispatchException = ex;  
55.            }  
56.            catch (Throwable err) {  
57.                // As of 4.3, we're processing Errors thrown from handler methods as well,  
58.                // making them available for @ExceptionHandler methods and other scenarios.  
59.                dispatchException = new NestedServletException("Handler dispatch failed", err);  
60.            }  
61.            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);  
62.        }  
63.        catch (Exception ex) {  
64.            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);  
65.        }  
66.        catch (Throwable err) {  
67.            triggerAfterCompletion(processedRequest, response, mappedHandler,  
68.                    new NestedServletException("Handler processing failed", err));  
69.        }  
70.        finally {  
71.            if (asyncManager.isConcurrentHandlingStarted()) {  
72.                // Instead of postHandle and afterCompletion  
73.                if (mappedHandler != null) {  
74.                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);  
75.                }  
76.            }  
77.            else {  
78.                // Clean up any resources used by a multipart request.  
79.                if (multipartRequestParsed) {  
80.                    cleanupMultipart(processedRequest);  
81.                }  
82.            }  
83.        }  
84.    }  

涉及責任鏈模式的核心的代碼就是如紅色字體標識:第3行,定義了一個責任鏈的數據結構,如下圖所示,屬於數組模式:

1.public class HandlerExecutionChain {  
2.  
3.    private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);  
4.  
5.    private final Object handler;  
6.  
7.    private HandlerInterceptor[] interceptors;  
8.  
9.    private List<HandlerInterceptor> interceptorList;  
10.  
    private int interceptorIndex = -1; 

定義了 攔截器數組List interceptorList,相應地也提供了初始化interceptorList的函數,如下圖所示:

1.public void addInterceptor(HandlerInterceptor interceptor) {  
2.        initInterceptorList().add(interceptor);  
3.    }  
4.  
5.    public void addInterceptors(HandlerInterceptor... interceptors) {  
6.        if (!ObjectUtils.isEmpty(interceptors)) {  
7.            CollectionUtils.mergeArrayIntoCollection(interceptors, initInterceptorList());  
8.        }  
9.    }  
10.  
11.    private List<HandlerInterceptor> initInterceptorList() {  
12.        if (this.interceptorList == null) {  
13.            this.interceptorList = new ArrayList<HandlerInterceptor>();  
14.            if (this.interceptors != null) {  
15.                // An interceptor array specified through the constructor  
16.                CollectionUtils.mergeArrayIntoCollection(this.interceptors, this.interceptorList);  
17.            }  
18.        }  
19.        this.interceptors = null;  
20.        return this.interceptorList;  
21.    }  

第11行,根據請求報文判斷出具體的handler,以及handler綁定的攔截器責任鏈。第39行,根據責任鏈節點數組,依次執行責任鏈節點的前處理函數,如下圖所示:第44行,調用核心處理器邏輯。
第51行,根據責任鏈節點數組,依次執行責任鏈節點的後處理函數,如下圖所示: 那麼核心問題來了,SpringMVC如何實現動態添加責任鏈節點呢,事實上SpringMVC容器在啓動的時候,會讀取配置文件,如下圖所示,加載所有的自定義的攔截器(實現HandlerInterceptorAdapter接口 )。

1.<mvc:interceptors>  
2.  <mvc:interceptor>  
3.    <mvc:mapping path="/**"/>  
4.    <mvc:exclude-mapping path="/login"/>     
5.    <mvc:exclude-mapping path="/index"/>  
6.    <bean class="package.interceptor.XXInterceptor"/>  
7.  </mvc:interceptor>  
8.</mvc:interceptors>  

4. 架構設計中的應用示例

1. 基於數據庫配置-初始版本

在這裏插入圖片描述
在架構設計中,責任鏈模式一般適用於系統的公共調度處理,當請求接入後,系統讀取責任鏈的配置信息並轉換成內存中的責任鏈數組,調度程序遍歷數組中的節點信息,依次處理節點的業務處理邏輯。初始版本的缺點是每個請求都要讀取配置文件或數據庫,性能會差。優點是當需要動態添加或刪除責任鏈節點時,可以實時生效。

2. 基於數據庫配置-升級版本

在這裏插入圖片描述

升級版本與SpringMVC的處理方式也有差異。SpringMVC在系統啓動時,會讀取責任鏈節點配置文件,加載到容器裏,但當更新責任鏈節點後,需要重新啓動應用。升級版本在配置文件與程序間添加了一個緩存,總控程序直接讀取緩存信息,性能相比初始版本有了提升,同時提供緩存刷新功能,當需要動態更新責任鏈節點時,可以觸發刷新,從而實現不重啓應用,即可實時更新配置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章