0%

设计模式-职责链在过滤器、拦截器的应用实现

今天,我们就通过 Servlet Filter、Spring Interceptor 这两个 Java 开发中常用的组件,来具体讲讲它在框架开发中的应用。

Servlet Filter

Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是 Servlet 规范的一部分,所以,只要是支持 Servlet 的 Web 容器(比如,Tomcat、Jetty 等),都支持过滤器功能。为了帮助你理解,我画了一张示意图阐述它的工作原理,如下所示。

在实际项目中,我们该如何使用 Servlet Filter 呢?我写了一个简单的模拟示例代码,如下所示。首先定义如下 Filter 接口,并实现 AFilter 和 BFilter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 自定义接口,如同 javax.servlet.Filter
public interface Filter {
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain);
}
public class AFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
System.out.println("AFilter execute Start...");
chain.doFilter(request, response);
System.out.println("AFilter execute End...");
}
}
public class BFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
System.out.println("BFilter execute Start...");
chain.doFilter(request, response);
System.out.println("BFilter execute End...");
}
}

其中 FilterChain 就是处理器链,由于 Servlet 只是一个规范,并不包含具体的实现,所以,Servlet 中 的 FilterChain 只是一个接口定义。具体的实现类由遵从 Servlet 规范的 Web 容器来提供,比如,ApplicationFilterChain 类就是 Tomcat 提供的 FilterChain 的实现类,下面我们给出 FilterChain 和 ApplicationFilterChain 的模拟示例代码。

为了让代码更易读懂,我对代码进行了简化,只保留了跟设计思路相关的代码片段。完整的代码你可以自行去 Tomcat 中查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface FilterChain {
void doFilter(ServletRequest request, ServletResponse response);
void addFilter(Filter filter);
}
public class ApplicationFilterChain implements FilterChain {
private List<Filter> filterList = new ArrayList<>();
private int pos = 0; //当前执行到了哪个filter
private int n = 0; //filter的个数

@Override
public void addFilter(Filter filter) {
this.filterList.add(filter);
n++;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos < n) {
Filter filter = filterList.get(pos++);
// 递归调用
filter.doFilter(request, response, this);
} else {
System.out.println("Start Execute Service...");
}

}
}

ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,实际上是一个递归调用。这样实现主要是为了在一个 doFilter() 方法中,支持双向拦截,既能拦截客户端发送来的请 求,也能拦截发送给客户端的响应。如下我们模拟执行上述过滤器示例。

1
2
3
4
5
6
7
8
9
public class Application {
public static void main(String[] args) {
FilterChain chain = new ApplicationFilterChain();
chain.addFilter(new AFilter());
chain.addFilter(new BFilter());
chain.addFilter(new CFilter());
chain.doFilter(new ServletRequest(), new ServletResponse());
}
}

执行结果如下所示:

1
2
3
4
5
6
7
AFilter execute Start...
BFilter execute Start...
CFilter execute Start...
Start Execute Service...
CFilter execute End...
BFilter execute End...
AFilter execute End...

Spring Interceptor

刚刚讲了 Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器。尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对 HTTP 请求进行拦截处理。

它们不同之处在于,Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。 Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。

在项目中,我们该如何使用 Spring Interceptor 呢?我写了一个简单的模拟示例。首先,自定义如下拦截器接口,并实现 AInterceptor 和 BInterceptor 两个拦截器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public interface HandlerInterceptor {

// 前置处理器
boolean preHandle(HttpServletRequest request, HttpServletResponse response);

// 后置处理器
void postHandle(HttpServletRequest request, HttpServletResponse response);

// 拦截器执行则执行该方法
void afterCompletion(HttpServletRequest request, HttpServletResponse response);
}
public class AInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Start execute AInterceptor...");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Close execute AInterceptor...");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Execute AInterceptor...");
}
}
public class BInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Start execute BInterceptor...");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Close execute BInterceptor...");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Execute BInterceptor...");
}
}

它也是基于职责链模式实现的。其中,HandlerExecutionChain 类是职责链模式中的处理器链。它的实现相较于 Tomcat 中的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。HandlerExecutionChain 的代码如下所示,同样,我对代码也进行了一些简化和修改,但大体思路并未改动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class HandlerExecutionChain {
private List<HandlerInterceptor> interceptors = new ArrayList<>();
private int interceptorIndex = -1; // 执行到哪个拦截器坐标

// 添加拦截器
public void addInterceptor(HandlerInterceptor interceptor) {
interceptors.add(interceptor);
}

// 批量调用拦截器的前置方法 preHandle
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {
for (HandlerInterceptor interceptor : interceptors) {
interceptorIndex++;
if (!interceptor.preHandle(request, response)) {
// 拦截器不在往下执行则执行已经被执行过的拦截器的 afterCompletion 方法
triggerAfterCompletion(request, response);
return false;
}
}
return true;
}

// 批量调用拦截器的后置方法 interceptors
void applyPostHandle(HttpServletRequest request, HttpServletResponse response) {
for (int i = interceptors.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors.get(i);
interceptor.postHandle(request, response);
}
}

// 批量调用拦截器的 afterCompletion 方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response) {
for (int i = interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors.get(i);
interceptor.afterCompletion(request, response);
}
}
}

如下我们模拟 Spring Interceptor 的执行,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Application {

public static void main(String[] args) {
HandlerExecutionChain chain = new HandlerExecutionChain();
chain.addInterceptor(new AInterceptor());
chain.addInterceptor(new BInterceptor());
chain.addInterceptor(new CInterceptor());
HttpServletRequest httpServletRequest = new HttpServletRequest();
HttpServletResponse httpServletResponse = new HttpServletResponse();
boolean isNext = chain.applyPreHandle(httpServletRequest, httpServletResponse);
if (!isNext) {
return;
}
System.out.println("start execute Controller");
chain.applyPostHandle(httpServletRequest, httpServletResponse);
chain.triggerAfterCompletion(httpServletRequest, httpServletResponse);
}
}

执行结果如下:

1
2
3
4
5
6
7
Start execute AInterceptor...
Start execute BInterceptor...
start execute Controller
Close execute BInterceptor...
Close execute AInterceptor...
Execute BInterceptor...
Execute AInterceptor...

在 Spring 框架中,DispatcherServlet 的 doDispatch() 方法来分发请求,它在真正的业务逻辑执行前后,执行 HandlerExecutionChain 中的 applyPreHandle() 和 applyPostHandle() 函数,用来实现拦截的功能。具体的代码实现很简单,你自己应该能脑补出来,这里就不罗列了。感兴趣的话,你可以自行去查看。

------ 本文结束------