今天,我们主要讲解职责链模式的原理和实现。除此之外,我还会利用职责链模式,带你实现一个可以灵活扩展算法的敏感词过滤框架。下一节课,我们会更加贴近实战,通过剖析Servlet Filter、Spring Interceptor来看,如何利用职责链模式实现框架中常用的过滤器、拦截器。
职责链模式的原理和实现
将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过A处理器处理,然后再把请求传递给B处理器,B处理器处理完后再传递给C处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。
第一种实现如下:Handler 是所有处理器类的抽象父类,handle()是抽象方法,每个具体的处理器类HandlerA 和HadnlerB的handle() 函数的代码结构类似,如果它能处理该请求就不继续往下传递,如果不能处理就需要往下传递了。
public abstract class Handler {protected Handler successor = null;public void setSuccessor(Handler successor) {this.successor = successor;}public abstract void handle();
}public class HandlerA extends Handler {@Overridepublic void handle() {boolean handled = false;//...if (!handled && successor != null) {successor.handle();}}
}public class HandlerB extends Handler {@Overridepublic void handle() {boolean handled = false;//...if (!handled && successor != null) {successor.handle();} }
}public class HandlerChain {private Handler head = null;private Handler tail = null;public void addHandler(Handler handler) {handler.setSuccessor(null);if (head == null) {head = handler;tail = handler;return;}tail.setSuccessor(handler);tail = handler;}public void handle() {if (head != null) {head.handle();}}
}// 使用举例
public class Application {public static void main(String[] args) {HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handle();}
}
实际上,上面的代码实现不够优雅。处理器类的handle()函数,不仅包含自己的业务逻辑,还包含对下一个处理器的调用,也就是代码中的successor.handle()。一个不熟悉这种代码结构的程序员,在添加新的处理器类的时候,很有可能忘记在handle()函数中调用successor.handle(),这就会导致代码出现bug。
针对这个问题,我们对代码进行重构,利用模板模式或者接口,将调用successor.handle()的逻辑从具体的处理器类中剥离出来,放到抽象父类中。这样具体的处理器类只需要实现自己的业务逻辑就可以了。重构之后的代码如下所示:
public interface IHandler {boolean handle();
}public class HandlerA implements IHandler {@Overridepublic boolean handle() {boolean handled = false;//...return handled;}
}public class HandlerB implements IHandler {@Overridepublic boolean handle() {boolean handled = false;//...return handled;}
}public class HandlerChain {private List<IHandler> handlers = new ArrayList<>();public void addHandler(IHandler handler) {this.handlers.add(handler);}public void handle() {for (IHandler handler : handlers) {boolean handled = handler.handle();if (handled) {break;}}}
}// 使用举例
public class Application {public static void main(String[] args) {HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handle();}
}
实际上,职责链模式还有一种变体,那就是请求会被所有的处理器都处理一遍,不存在中途终止的情况。这种变体也有两种实现方式:用链表存储处理器和用数组存储处理器,跟上面的两种实现方式类似,只需要稍微修改即可。
仅仅将handle代码的if判断去掉即可
public void handle() {for (IHandler handler : handlers) {boolean handled = handler.handle();}}
职责链模式的应用场景举例
对于支持UGC(User Generated Content,用户生成内容)的应用(比如论坛)来说,用户生成的内容(比如,在论坛中发表的帖子)可能会包含一些敏感词(比如涉黄、广告、反动等词汇)。针对这个应用场景,我们就可以利用职责链模式来过滤这些敏感词。
public interface SensitiveWordFilter {boolean doFilter(Content content);
}public class SexyWordFilter implements SensitiveWordFilter {@Overridepublic boolean doFilter(Content content) {boolean legal = true;//...return legal;}
}// PoliticalWordFilter、AdsWordFilter类代码结构与SexyWordFilter类似public class SensitiveWordFilterChain {private List<SensitiveWordFilter> filters = new ArrayList<>();public void addFilter(SensitiveWordFilter filter) {this.filters.add(filter);}// return true if content doesn't contain sensitive words.public boolean filter(Content content) {for (SensitiveWordFilter filter : filters) {if (!filter.doFilter(content)) {return false;}}return true;}
}public class ApplicationDemo {public static void main(String[] args) {SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();filterChain.addFilter(new AdsWordFilter());filterChain.addFilter(new SexyWordFilter());filterChain.addFilter(new PoliticalWordFilter());boolean legal = filterChain.filter(new Content());if (!legal) {// 不发表} else {// 发表}}
}
首先,我们来看,职责链模式如何应对代码的复杂性。
将大块代码逻辑拆分成函数,将大类拆分成小类,是应对代码复杂性的常用方法。应用职责链模式,我们把各个敏感词过滤函数继续拆分出来,设计成独立的类,进一步简化了SensitiveWordFilter类,让SensitiveWordFilter类的代码不会过多,过复杂。
其次,我们再来看,职责链模式如何让代码满足开闭原则,提高代码的扩展性。
当我们要扩展新的过滤算法的时候,比如,我们还需要过滤特殊符号,按照非职责链模式的代码实现方式,我们需要修改SensitiveWordFilter的代码,违反开闭原则。不过,这样的修改还算比较集中,也是可以接受的。而职责链模式的实现方式更加优雅,只需要新添加一个Filter类,并且通过addFilter()函数将它添加到FilterChain中即可,其他代码完全不需要修改。
上面介绍了如何责任链模式的原理和应用场景,那么接下来我们看一下责任链模式在框架中的应用,为框架提供可靠的扩展点。
Servlet Filter
Servlet Filter是Java Servlet规范中定义的组件,翻译成中文就是过滤器,它可以实现对HTTP请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是Servlet规范的一部分,所以,只要是支持Servlet的Web容器(比如,Tomcat、Jetty等),都支持过滤器功能。为了帮助你理解,我画了一张示意图阐述它的工作原理,如下所示。
在实际项目中,我们该如何使用Servlet Filter呢?我写了一个简单的示例代码,如下所示。添加一个过滤器,我们只需要定义一个实现javax.servlet.Filter接口的过滤器类,并且将它配置在web.xml配置文件中。Web容器启动的时候,会读取web.xml中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由Servlet来处理。主要是涉及到过滤器的处理范围。
public class LogFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 在创建Filter时自动调用,// 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件中读取的)}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("拦截客户端发送来的请求.");chain.doFilter(request, response);System.out.println("拦截发送给客户端的响应.");}@Overridepublic 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>
职责链模式的实现包含处理器接口(IHandler)或抽象类(Handler),以及处理器链(HandlerChain)。对应到Servlet Filter,javax.servlet.Filter就是处理器接口,FilterChain就是处理器链。接下来,我们重点来看FilterChain是如何实现的。
public final class ApplicationFilterChain implements FilterChain {private int pos = 0; //当前执行到了哪个filterprivate int n; //filter的个数private ApplicationFilterConfig[] filters;private Servlet servlet;@Overridepublic void doFilter(ServletRequest request, ServletResponse response) {if (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];Filter filter = filterConfig.getFilter();filter.doFilter(request, response, this);} else {// filter都处理完毕后,执行servletservlet.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;}
}
Spring Interceptor
刚刚讲了Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器。尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对HTTP请求进行拦截处理。
它们不同之处在于,Servlet Filter是Servlet规范的一部分,实现依赖于Web容器。Spring Interceptor是Spring MVC框架的一部分,由Spring MVC框架来提供实现。客户端发送的请求,会先经过Servlet Filter,然后再经过Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。
·
public class LogInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("拦截客户端发送来的请求.");return true; // 继续后续的处理}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("拦截发送给客户端的响应.");}@Overridepublic 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>
同样,我们还是来剖析一下,Spring Interceptor底层是如何实现的。
当然,它也是基于职责链模式实现的。其中,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);}}}}
}
注意看代码里面的 applyPreHandle 和 applyPostHandle 的循环方式,一个是前项循环一个是后项循环,而且,在applyPreHandle执行为false时, triggerAfterCompletion 也是要执行的。