职责链模式
如何实现可灵活扩展算法的敏感信息过滤框架?
职责链模式的原理与实现
- Chain Of Responsibility Design Pattern: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. 将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
- 每个处理器各自承担各自的处理职责。
- 两种常见实现: 第一种。Handler是所有处理器类的抽象父亲,handle()是抽象方法,每个具体的处理器类的handle函数的代码结构类似,如果他能处理该请求就不继续往下传递,如果不能处理则交友后面的处理器处理。HandlerChain是处理器链,从数据结构的角度来看,他是一个记录链头链尾的链表,记录链尾是为了方便添加处理器。
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 { @Override public void handle() { boolean handled = false; //... if (!handled && successor != null) { successor.handle(); } } } public class HandlerB extends Handler { @Override public 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(),不熟悉这个代码的人可能在添加新handler的时候忘记调用对下一个处理的调用进而产生bug。
可以用模板模式重构:将对下一个处理器的调用放到抽象父类中,具体的处理器类只需要实现自己的业务逻辑即可。
public abstract class Handler { protected Handler successor = null; public void setSuccessor(Handler successor) { this.successor = successor; } public final void handle() { boolean handled = doHandle(); if (successor != null && !handled) { successor.handle(); } } protected abstract boolean doHandle(); } public class HandlerA extends Handler { @Override protected boolean doHandle() { boolean handled = false; //... return handled; } } public class HandlerB extends Handler { @Override protected boolean doHandle() { boolean handled = false; //... return handled; } } // HandlerChain和Application代码不变
- 第二种常见实现: HandlerChain用数组而非链表保存所有的处理器,并且需要再HandlerChain的handle函数中依次调用每个处理的handle函数。
public interface IHandler { boolean handle(); } public class HandlerA implements IHandler { @Override public boolean handle() { boolean handled = false; //... return handled; } } public class HandlerB implements IHandler { @Override public 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(); } }
- 在定义中如果handler不能处理请求他就会往下传递,但是实际上还有一种变体:请求会被所有的处理器处理一遍,这种变体也有链表和数组两种实现,与之前的代码类似。
public abstract class Handler { protected Handler successor = null; public void setSuccessor(Handler successor) { this.successor = successor; } public final void handle() { doHandle(); if (successor != null) { successor.handle(); } } protected abstract void doHandle(); } public class HandlerA extends Handler { @Override protected void doHandle() { //... } } public class HandlerB extends Handler { @Override protected void doHandle() { //... } } 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(); } }
public interface IHandler { boolean handle(); } public class HandlerA implements IHandler { @Override public boolean handle() { boolean handled = false; //... return handled; } } public class HandlerB implements IHandler { @Override public 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(); } } } // 使用举例 public class Application { public static void main(String[] args) { HandlerChain chain = new HandlerChain(); chain.addHandler(new HandlerA()); chain.addHandler(new HandlerB()); chain.handle(); } }
应用场景举例
- 对于支持UGC(User Generated Content, 用户生成内容)的应用(比如论坛)来说,用户生成的内容可能包含一些敏感词,针对这个应用场景,可以利用职责链模式。
- 对于处理敏感词,我么可以直接禁止发布,另一种是给敏感词打码之后发布。第一种处理方式符合GoF给出的职责链模式的定义,第二种是职责链模式的变体。
- 第一种的代码实现:
public interface SensitiveWordFilter { boolean doFilter(Content content); } public class SexyWordFilter implements SensitiveWordFilter { @Override public 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 { // 发表 } } }
对比一种简单实现:
public class SensitiveWordFilter { // return true if content doesn't contain sensitive words. public boolean filter(Content content) { if (!filterSexyWord(content)) { return false; } if (!filterAdsWord(content)) { return false; } if (!filterPoliticalWord(content)) { return false; } return true; } private boolean filterSexyWord(Content content) { //.... } private boolean filterAdsWord(Content content) { //... } private boolean filterPoliticalWord(Content content) { //... } }
设计模式主要是为了应对代码的复杂性,让其满足开闭原则,提高代码的扩展性。
应对代码的复杂性
大块代码被拆分成更小的类。
满足开闭原则,提高扩展性
当需要扩展新的过滤算法的时候,我们只需要新加一个Filter类并将它添加到FilterChain中,其他的地方不需要修改。
ApplicationDemo可以看成是客户端代码,除了ApplicationDemo之外的是敏感词过滤框架代码。
假设敏感词过滤框架代码是第三方框架,我们扩展一个新的算法,没法直接修改框架代码,利用职责链模式我们就可以扩展新的功能。
框架中常用的过滤器、拦截器是如何实现的?
结合Servlet Filter / Spring Interceptor 介绍职责链模式在框架开发中的应用。
Servlet Filter
Servlet Filter 可以实现对HTTP请求的过滤功能,比如鉴权、限流、记录日志、验证参数等。他是Servlet规范的一部分,所以只要是支持Servlet的Web容器(Tomcat、Jetty)都支持过滤器功能。
使用: 定义一个实现javax.servlet.Filter
接口的过滤器,并且配置在web.xml中。容器启动的时候会先读取web.xml配置创建过滤器对象,请求来的时候会先经过过滤器然后才交给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>
如何实现的
tomcat
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; } }
doFilter中的递归调用是为了支持双向拦截,既能拦截客户端发来的请求,也能拦截发给客户端的请求。
Spring Interceptor
可以看成跟Servlet Filter一个概念,不过他是属于SpringMVC的一部分。
使用
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>
LogFilter 对请求和响应的拦截是在 doFilter() 一个函数中实现的,而 LogInterceptor 对请求的拦截在 preHandle() 中实现,对响应的拦截在 postHandle() 中实现。
在 Spring 框架中,DispatcherServlet 的 doDispatch() 方法来分发请求,它在真正的业务逻辑执行前后,执行 HandlerExecutionChain 中的 applyPreHandle() 和 applyPostHandle() 函数,用来实现拦截的功能。
实现
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); } } } } }