责任链模式详解

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在系统启动时,会读取责任链节点配置文件,加载到容器里,但当更新责任链节点后,需要重新启动应用。升级版本在配置文件与程序间添加了一个缓存,总控程序直接读取缓存信息,性能相比初始版本有了提升,同时提供缓存刷新功能,当需要动态更新责任链节点时,可以触发刷新,从而实现不重启应用,即可实时更新配置。

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