本文章为极客时间《设计模式之美》专栏学习笔记。
一、定义
在 Gof 的《设计模式》中,是这么定义的:(译成中文)将请求的发送和接受解耦,让多个接受对象都有机会处理这个请求,将这些接受对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接受对象能够处理它为止。
这么说有些抽象,具体些:“接受对象” 就是多个处理器,多个处理器会依次处理同一个请求,一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理完再传给 C,以此类推,形成一个链条,链条上的每个处理器各自承担着各自的处理职责,所以叫职责链模式。
二、实现
来看看代码实现,从数据结构上,分为数组和链表实现;从思路上来说,处理器要有 handle 处理方法,还需要体现数据结构。
1、数组实现
提供接口:
public interface Handler {
// 处理方法
boolean handle();
}
处理器实现类:
public class Handler1 implements Handler {
@Override
public boolean handle() {
System.out.println("handler1执行");
return true;
}
}
再来一个实现类:
public class Handler2 implements Handler {
@Override
public boolean handle() {
System.out.println("handler2执行");
return true;
}
}
然后需要一个数组结构,并且依次调用每个处理器的 handler 方法:
public class ArrayHandlerChain {
static ArrayList<Handler> chainList = new ArrayList(2);
// 构造数组
public void addHandler(Handler handler) {
chainList.add(handler);
}
// 挨个遍历
public void handle(){
for (Handler handler : chainList) {
if(!handler.handle()){
break;
}
}
}
}
启动类:
@RestController
@SpringBootApplication
public class BootClass {
public static void main(String[] args) {
SpringApplication.run(BootClass.class, args);
}
@GetMapping("/arrayHandler")
public void arrayHandler() {
ArrayHandlerChain arrayHandlerChain = new ArrayHandlerChain();
arrayHandlerChain.addHandler(new Handler1());
arrayHandlerChain.addHandler(new Handler2());
arrayHandlerChain.handle();
}
}
在 Gof 的定义中,如果处理链上的某个处理器能处理这个请求,那么就不会再继续往下传递请求了,不过实际上职责链也可以变体,就是请求可以被所有的处理器都处理一遍,不存在中途终止的情况,视需求而定,比如,只需要执行处理器1,而不需要执行处理器2 的话,改造下 1 即可:
public class Handler1 implements Handler {
@Override
public boolean handle() {
if (true) {
System.out.println("handler1执行,遇到特殊情况无需执行handler2");
return false;
}
System.out.println("handler1执行");
return true;
}
}
2、链表实现
提供个抽象类: (我也不知道为啥专栏里对于链表实现首先是提供抽象类,而不是接口,按说,如果 setSuccessor 不想被重写的话,jdk1.8 接口已经新增了静态和默认方法,静态的不能被重写、默认的可重写也可不重写;可能是因为在同一个包下,方法名又一样,都写成接口的话不易区分???)
public abstract class Handler {
Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public Handler getSuccessor() {
return successor;
}
public abstract boolean handle();
}
很熟悉的链表配方吧,节点类(Handler)里需要定义一个节点对象(successor),作为下一个节点,(本来节点应该包括两部分的,数据和下一个节点,不过这里用作处理器,也不需要啥数据,有个 handle() 方法就行)。
封装一个有handle方的链表结构:
public class LinkedHandlerChain {
Handler head;
Handler tail;
// 入口
public void handle() {
if (head != null) {
head.handle();
}
}
// 设置头尾节点的规则
public void addHandler(Handler handler) {
if (head == null) {
head = handler;
tail = handler;
} else {
tail.setSuccessor(handler);
tail = handler;
}
}
}
实现:
public class Handler1 extends Handler{
@Override
public boolean handle() {
// 是否执行该处理器
boolean isExecute = true;
if (isExecute) {
// 执行当前
System.out.println("handler1执行");
}
if (this.getSuccessor() != null) {
this.getSuccessor().handle();
}
return isExecute;
}
}
再来一个:
public class Handler2 extends Handler {
@Override
public boolean handle() {
// 是否执行该处理器
boolean isExecute = ture;
if (isExecute) {
// 执行当前
System.out.println("handler2执行");
}
if (this.getSuccessor() != null) {
this.getSuccessor().handle();
}
return isExecute;
}
}
只需要执行处理器2,而不需要执行处理器1 的话,改造下 1,将 isExecute 设置为 false 即可。
三、好处
职责链模式能让代码满足开闭原则(对扩展开发,对修改关闭,也就是说尽量通过扩展的形式来实现变化,而不是通过修改代码的形式来实现变化 我靠,发现我工作完全违反了这个原则是怎么回事😅),比如说,论坛系统,用户发布的内容可能包含敏感词,对于敏感词,可以直接禁止发布;也可以用 * 替换处理,这两种方式都可以用职责链处理,这里的链就是各个敏感词处理器串起来的,当需要添加新的过滤算法时,只需要添加到职责链即可;
假设说敏感词过滤框架不是我们自己开发维护的,而是引入的一个第三方框架,那如果我们想扩展其中一个过滤算法,就可以利用职责链模式了,不用去修改框架源码,而扩展新的功能,在框架这个代码范围内实现了开闭原则。而且,过滤算法也更灵活,可以只选择使用其中某几个过滤算法。
四、框架中的实例
职责链模式常用来开发框架的过滤器和拦截器,比如,Servlet Filter 和 Spring Interceptor 两个 Java 开发中常用的组件。
1、Servlet Filter
Servlet Filter 是 Java Servlet 规范中定义的组件,可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、验证参数等等(这里给了我提示,写代码的时候注意抽出来,而不是逮着 request 操作)。既然是 Servlet 的一部分,那么支持 Servlet 的 Web 容器(比如 Tomcat、Jetty )就都支持过滤器功能。
其实刚学习 Servlet 的时候,是有学习过过滤器的用法的,比如:
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 在创建Filter时自动调用,
// 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件中读取的)
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("拦截客户端发送来的请求.");
chain.doFilter(request, response);
System.out.println("拦截发送给客户端的响应.");
}
@Override
public void destroy() {
// 在销毁Filter时自动调用
}
}
在web.xml配置文件中如下配置:
<filter>
<filter-name>logFilter</filter-name>
<filter-class>com.xzg.cd.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>logFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在二、实现中,可以看到,职责链模式的实现包含处理器接口(interface Handler 或抽象类 Handler),以及处理器链(HandlerChain)。对应到 Servlet Filter,javax.servlet.Filter 就是处理器接口,FilterChain 就是处理器链。
接下来,看看 FilterChain 是如何实现的。不过 Servlet 只是一个规范,并不包含具体的实现,所以,Servlet 中的 FilterChain 只是一个接口定义。具体的实现类由遵从 Servlet 规范的 Web 容器来提供,比如,ApplicationFilterChain 类就是 Tomcat 提供的 FilterChain 的实现类,简化的代码如下所示 ,只保留了跟设计思路相关的代码片段。
public final class ApplicationFilterChain implements FilterChain {
private int pos = 0; //当前执行到了哪个filter
private int n; //filter的个数
private ApplicationFilterConfig[] filters;
private Servlet servlet;
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
} else {
// filter都处理完毕后,执行servlet
servlet.service(request, response);
}
}
public void addFilter(ApplicationFilterConfig filterConfig) {
for (ApplicationFilterConfig filter:filters)
if (filter==filterConfig)
return;
if (n == filters.length) {//扩容
ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
}
ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,实际上是一个递归调用。 你可以用每个 Filter(比如 LogFilter)的 doFilter() 的代码实现,直接替换 ApplicationFilterChain 的第 12 行代码,一眼就能看出是递归调用了。替换了一下,如下所示。
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
//filter.doFilter(request, response, this);
//把filter.doFilter的代码实现展开替换到这里
System.out.println("拦截客户端发送来的请求.");
chain.doFilter(request, response); // chain就是this
System.out.println("拦截发送给客户端的响应.")
} else {
// filter都处理完毕后,执行servlet
servlet.service(request, response);
}
}
具体分析:
在 LogFilter 里,调用到 doFilter,第一次是走到 if 分支里的,递归调用它本身 filter.doFilter,这时就把请求拦截了,然后 pos++=n 了,这一次(即第二次) doFilter 会走到 else 分支,执行 filter.doFilter,第二次 doFilter 方法执行结束,回到第一次执行 doFilter 的地方,整个 doFilter 也就执行完了,对于 chain.doFilter,会继续往下执行,这时就把响应拦截了。
2、Spring Interceptor
它们不同之处在于,Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。
在项目中,比如以下例子,LogInterceptor 实现的功能跟刚才的 LogFilter 完全相同,只是实现方式上稍有区别。LogFilter 对请求和响应的拦截是在 doFilter() 一个函数中实现的,而 LogInterceptor 对请求的拦截在 preHandle() 中实现,对响应的拦截在 postHandle() 中实现。
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截客户端发送来的请求.");
return true; // 继续后续的处理
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截发送给客户端的响应.");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("这里总是被执行.");
}
}
//在Spring MVC配置文件中配置interceptors
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<bean class="com.xzg.cd.LogInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
它也是基于职责链模式实现的。其中,HandlerExecutionChain 类是职责链模式中的处理器链。它的实现相较于 Tomcat 中的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。HandlerExecutionChain 的源码如下所示,同样,进行了一些简化,只保留了关键代码。
public class HandlerExecutionChain {
private final Object handler;
private HandlerInterceptor[] interceptors;
public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList().add(interceptor);
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = 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);
}
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = 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 ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}
TODO
一些框架也用的是职责链模式,摘自评论:Okhttp;在Netty里面,pipeline组件就是使用职责链模式进行组装的,底层是双向链表(首尾节点是哨兵节点,用于处理某些buffer的释放),详细可以参考这个文章 https://mp.weixin.qq.com/s/Lh1GnhYf36C2Y2gdBJvKQQ;分析了 shiro 的过滤器实现,写了笔记,附上地址:https://www.jianshu.com/p/1e7a0384da6d