概念
将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
职责链模式有两种执行方式,一种是GoF定义中说明的,如果执行器链上某个处理器能够处理请求,那么就不会继续往下执行。另一种变体是,请求会被所有执行器链上的所有处理器都处理一遍,不存在中途退出的情况。
实现方式同样有两种,一种是利用对象引用保存下一个关联的处理器,类似于记录链表的链头与链尾;另一种是利用数组存储所有处理器,执行时需要逐一遍历。
使用方式
关键点
- 定义处理器接口 或 抽象类
- 定义处理器链
假设有这样的场景,接口需要对参数进行敏感词校验,例如博客需要校验文章是否包含敏感词,如政治、广告、黄色等敏感词则需要过滤。
过滤也有两种策略情况:
- 打回策略,一种是发现文章包含了某类敏感词,那么立刻将文章打回,禁止发布;
- 替换策略,另一种是发现文章包含了某类敏感词,不打回文章但会将敏感词会用 *** 代替。
这两种过滤的方式就可以使用到职责链的两种执行方式,一种中途退出,一种全部执行。
因为通过数组的实现方式较为简单,不需要考虑链表先后的顺序,下面用数组实现的方式来实现这种场景。
一、打回策略 - 中断执行
1. 定义敏感词过滤接口
public interface SensitiveWordFilter {
boolean doFilter(String content);
}
2. 不同的过滤方式实现接口
- 政治敏感词过滤器
public class PoliticsSensitiveWordFilter implements SensitiveWordFilter{
@Override
public boolean doFilter(String content) {
boolean isLegal = content.contains("政治");
System.out.println("政治敏感词过滤");
return isLegal;
}
}
- 广告敏感词过滤器
public class AdsSexySensitiveWordFilter implements SensitiveWordFilter{
@Override
public boolean doFilter(String content) {
System.out.println("广告词过滤");
return content.contains("广告");
}
}
- 黄色敏感词过滤器
public class SexySensitiveWordFilter implements SensitiveWordFilter{
@Override
public boolean doFilter(String content) {
System.out.println("过滤黄色敏感词");
return content.contains("黄色");
}
}
3. 过滤器执行链
public class SensitiveWordFilterChain {
// 数组保存链处理器
private List<SensitiveWordFilter> filters = new LinkedList<>();
public void addFilter(SensitiveWordFilter filter) {
filters.add(filter);
}
/**
* 遍历过滤器都执行一遍,如果发现有敏感词,则打回文章
* @param content 校验的内容
*/
public boolean doChain(String content) {
for(SensitiveWordFilter filter : filters) {
if(filter.doFilter(content)) {
return false;
}
}
return true;
}
}
4. 客户端执行
/***
* 实现方式:数组保存过滤器
* 执行目标:其中一条过滤器不满足就停止过滤
*/
@Test
public void testChainFilter() {
String content = "打打杀杀"; // 文章内容
// 注册过滤器
SensitiveWordFilterChain chain = new SensitiveWordFilterChain();
chain.addFilter(new SexySensitiveWordFilter());
chain.addFilter(new AdsSexySensitiveWordFilter());
chain.addFilter(new PoliticsSensitiveWordFilter());
// 执行过滤
System.out.println(chain.doChain(content));
}
5. 过滤器执行链的工厂方法
这里为了方便测试,注册过滤器就直接在客户端上执行了。但由于每次执行都要手动注册过滤器,终究不太好。另外执行器链可能不只一个,也可能存在其它类型的过滤器,例如过滤xss、SQL 注入之类的,就需要另外一种执行器链去区分了。
所以这里可以通过一个工厂方法去初始化过滤器,并获取到对应的执行器链。而获取到不同的执行器链又可以考虑使用策略模式去处理,这里就省去了策略模式的东西了。
/**
* 过滤器执行链工厂方法
*/
public class FilterFactory {
private static SensitiveWordFilterChain sensitiveWordFilterChain = new SensitiveWordFilterChain();
/**
* 初始化过滤器执行链
*/
static {
// 初始化敏感词执行器链
initSensitiveWordFilter();
// 初始化xss、SQL 安全信息执行器链
initXssSQLFilterChain();
}
private static void initSensitiveWordFilter() {
sensitiveWordFilterChain.addFilter(new SexySensitiveWordFilter());
sensitiveWordFilterChain.addFilter(new AdsSexySensitiveWordFilter());
sensitiveWordFilterChain.addFilter(new PoliticsSensitiveWordFilter());
}
private static void initXssSQLFilterChain() {
// init..
}
public static FilterChain getFilterChain() {
// TODO 动态获取执行器链,省去了用策略模式获取不同过滤器的方式...
return sensitiveWordFilterChain;
}
}
二、 替换策略 - 全部执行
替换策略其实就是将过滤接口的返回值改一下,返回当前过滤的对象就行。由于测试用的是String字符串,那么直接用String返回即可。
1. 定义敏感过滤器接口
public interface SensitiveWordFilter {
String doFilter(String content);
}
2. 不同的过滤方式实现接口
- 政治敏感词
public class PoliticsSensitiveWordFilter implements SensitiveWordFilter{
@Override
public String doFilter(String content) {
return content.replace("政治","***");
}
}
ps:实际场景的实现不会这么简单的,这里只讲思路;
3. 过滤器执行链
public class SensitiveWordFilterChain {
// 数组保存链处理器
private List<SensitiveWordFilter> filters = new LinkedList<>();
public void addFilter(SensitiveWordFilter filter) {
filters.add(filter);
}
/**
* 遍历过滤器都执行一遍,过滤所有的敏感词,将敏感词替换为***
* @param content 校验的内容
*/
public String doChain(String content) {
for(SensitiveWordFilter filter : filters) {
content = filter.doFilter(content); // 其实在疯狂创建字符串对象了 0.0
}
return content;
}
}
ps:这里跟打回策略不同的是,处理过滤器链时会将所有的过滤器都执行一遍 且 返回值是 String。
4. 客户端执行
与打回策略的执行方式一样的。
/***
* 实现方式:数组保存过滤器
* 执行目标:使用所有的敏感词过滤
*/
@Test
public void testChainFilter() {
String content = "打打杀杀"; // 文章内容
// 注册过滤器
SensitiveWordFilterChain chain = new SensitiveWordFilterChain();
chain.addFilter(new SexySensitiveWordFilter());
chain.addFilter(new AdsSexySensitiveWordFilter());
chain.addFilter(new PoliticsSensitiveWordFilter());
// 执行过滤
System.out.println(chain.doChain(content));
}
过滤器与拦截器
前面以敏感词的过滤为场景,用两种策略分别实现了中断执行和 全部执行两种方式。在日常开发中,所用到的框架有很多也是用职责链的思想进行扩展实现的。下面看下Servlet 的 Filter 和 Spring 的Interceptor。
Servlet Filter
Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是 Servlet 规范的一部分,所以,只要是支持 Servlet 的 Web 容器(比如,Tomcat、Jetty 等),都支持过滤器功能。
在实际项目中,如果想使用Servlet Filter,那么需要添加一个过滤器实现Filter接口,重写init、doFilter、destory三个方法。
Maven项目中需要引入servlet-api的依赖:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
新增 LogFilter
过滤器,重写Filter接口的方法
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("创建Filter时调用");
// FilterConfig 参数从配置文件web.xml中读取
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter...拦截客户端发送过来的请求");
// 实际上是个递归过程,一直向下递归,直到底,然后回溯,执行后面的方法
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("doFilter...拦截返回给客户端的响应");
}
@Override
public void destroy() {
System.out.println("销毁Filter时调用");
}
}
由于需要在启动时被初始化,所以需要在web.xml配置文件中,配置该Filter对象的路径。这样当请求来时,Web容器会先经过Filter过滤器,然后才由Servlet处理。
// web.xml里的配置
<filter>
<filter-name>logFilter</filter-name>
<filter-class>behavior.chain.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>logFilter</filter-name>
<url-pattern>/*</url-pattern> // 拦截的路径
</filter-mapping>
Spring Interceptor
Spring Interceptor 是Spring MVC 框架的一部分,由SpringMvc框架来提供实现。翻译成中文就是拦截器,跟Servlet Filter功能类似,但不同在于Servlet Filter是Servlet规范的一部分,实现依赖于web容器。
在web请求中,请求会先经过Servlet Filter,然后再经过Spring Interceptor ,最后到达具体的业务代码中。
如果想要使用Spring Interceptor,那么可以实现 由SpringMvc 提供的HandlerInterceptor 接口。
- LogInterceptor
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="behavior.chain.LogInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
当然,它也是基于职责链模式实现的。其中,HandlerExecutionChain 类是职责链模式中的处理器链。它的实现相较于 Tomcat 中的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。
总结
职责链在开发中,常用来实现框架的拦截器、过滤器功能,让使用者在不需要修改框架源码的情况下,添加新的过滤拦截功能。体现了对外扩展开放,对修改关闭的设计原则。
在实际开发中,Servlet Filter、Spring Interceptor、Aop都可以实现鉴权、限流、日志等功能,由于三者的粒度不同,导致他们的应用范围也不同。例如Servlet Filter 是Servlet的规范,那么只要是实现Servlet规范的容器,都可以应用这个,而Spring Interceptor 必须依赖于Spring框架。
此外,三者的执行先后也有不同。请求过来时先执行了Filter、才会执行Interceptor,而 Aop 是关注在方法层面,在执行某方法之时做代理拦截。