态度决定一切

0%

Filter 和 Interceptor 比较

  1. 过滤器(Filter)
  2. 拦截器(Interceptor

Filter

  • Filter是servlet规范中定义的java web组件, 在所有支持java web的容器中都可以使用

  • Filter和Filter Chain是密不可分的, Filter可以实现依次调用正是因为有了Filter Chain

上图是Filter对请求进行拦截的原理图, 那么java web容器(以tomcat为例子)是如何实现这个功能的呢?

下面看下Filter和Filter Chain的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Filter
public interface Filter {

// 容器创建的时候调用, 即启动tomcat的时候调用
public void init(FilterConfig filterConfig) throws ServletException;

// 由FilterChain调用, 并且传入Filter Chain本身
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;

// 容器销毁的时候调用, 即关闭tomcat的时候调用
public void destroy();
}

// FilterChain
public interface FilterChain {

// 由Filter.doFilter()中的chain.doFilter调用
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}
  • 正是因为Filter Chain在调用每一个Filter.doFilter()时将自身引用传递进去, 才实现了Filter的依次调用, 在Filter全部调用完之后再调用真正处理请求的servlet, 并且再次逆序回调Filter. 可能这么看还是不太明白是怎么实现Filter的顺序调用, 调用真正的servlet, 逆序调用Filter的, 一起看下Tomcat的源码就一目了然了.

  • 在tomcat中Filter Chain的默认实现是ApplicationFilterChain, 在ApplicationFilterChain中最关键的方法就是internalDoFilter, 整个Filter流程的实现就是由这个方法完成.

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
46
47
48
49
50
51
52
53
54
// internalDoFilter(只保留关键代码)
private void internalDoFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {

// Call the next filter if there is one
// pos: 当前的filter的索引, n: 调用链中所有的Filter的数量
// 如果调用链中还有没有调用的Filter就继续调用, 否则跳过if语句
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
// 获取Filter
Filter filter = filterConfig.getFilter();
if( Globals.IS_SECURITY_ENABLED ) {
...
其他代码
...
} else {
// 这句话是重点, 调用Filter的doFilter方法并把Filter Chain本身传进去(this)
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
...
异常处理代码
...
}
return;
}

// We fell off the end of the chain -- call the servlet instance
try {
...
其他代码
...
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
...
其他代码
...
} else {
// 调用真正的Filter
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
...
异常处理代码
...
} finally {
...
始终要执行的代码
...
}
}

Filter的正序调用的过程和调用真正的servlet的过程了, 但是Filter的逆序调用在哪里体现了呢?

  • 假设下面的Filter就是调用链中的最后一个Filter
1
2
3
4
5
6
7
8
9
public class LogFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {

Log.info("before");
chain.doFilter(request, response);
Log.info("after");
}
}

那么在调用chain.doFilter之后就跳过了if语句从而调用了真正的servlet, 然后internalDoFilter方法就结束(出栈)了, 紧接着就是调用Log.info(“after”)了, 然后LogFilter的doFilter就结束了(也出栈了), 紧接着就是internalDoFilter中filter.doFilter(request, response, this)的结束然后return, 然后就是调用上一个filter的chain.doFilter()之后的代码, 以此类推.

因此Filter调用链的实现其实就是一个方法调用链的过程. 刚开始, Filter Chain每调用一个Filter.doFilter()方法就是向方法调用栈中进行压栈操作(代码上的体现就是执行Filter.doFilter之前的代码), 当Filter全部调用完成之后就调用真正处理请求的servlet, 然后由方法调用链自动进行出栈操作(代码上的体现就是执行Filter.doFilter之后的代码), 从而完成整个Filter的调用链. 因为Filter功能实现实际上就是利用了方法的压栈出栈, 所以可以在调用chain.doFilter之前将方法返回, 让容器不在调用servlet方法, 从而实现权限的控制, 关键词的过滤等功能.

Interceptor

  • Interceptor不是servlet规范中的java web组件, 而是Spring提供的组件, 功能上和Filter差不多. 但是实现上和Filter不一样.

Interceptor功能的实现主要是在Spring Mvc的DispatcherServelt.doDispatch方法中, 让我们来看看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Interceptor的源码
public interface HandlerInterceptor {

// 在调用真正的处理请求类之前调用
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;

// 在调用真正的处理请求类之后调用
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;

// 在完成渲染或者出错之后调用
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;

}

doDispatch

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
 doDispatch源码(只保留关键代码)

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
....
其它的处理代码
....

// 调用拦截器的前置处理方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// Actually invoke the handler.
// 调用真正的处理请求的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// 找到渲染模版
applyDefaultViewName(processedRequest, mv);

// 调用拦截器的后置处理方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
....
异常处理代码
....
}
finally {
....
始终要执行的代码
....
}

其实看了doDispatch的关键代码, Spring Mvc对整个请求的处理流程已经很清楚了:

调用拦截器的前置方法 -> 调用处理请求的方法 -> 渲染模版 -> 调用拦截器的后置处理方法 -> 调用拦截器的完成方法

接下来看一看Spring Mvc是如何实现依次调用这么多拦截器的前置方法, 后置方法, 完成方法的。

进入到mapperHandler.applyPreHandle()方法中(调用拦截器的前置方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 如果拦截器数组不为空
if (!ObjectUtils.isEmpty(interceptors)) {
// 按顺序调用拦截器数组中的preHandle方法
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
// 如果拦截器的preHandle方法返回false, 则调用当前拦截器的triggerAfterCompletion方法, 然后返回, 并且不再调用后续的拦截器
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}

进入到mappedHandler.applyPostHandle()方法中(调用拦截器的后置方法)

1
2
3
4
5
6
7
8
9
10
11
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 如果拦截器数组不为空
if (!ObjectUtils.isEmpty(interceptors)) {
// 倒序调用拦截器数组中拦截器的postHandle方法
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}

不管是否出异常triggerAfterCompletion方法始终会被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {

HandlerInterceptor[] interceptors = getInterceptors();
// 拦截器数组不为空
if (!ObjectUtils.isEmpty(interceptors)) {
// 从成功执行的最后一个拦截器开始“逆序”调用afterCompletion方法
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);
}
}
}
}

注:triggerAfterCompletion会逆序调用afterCompletion方法

看过以上三个方法之后, Spring Mvc如何处理拦截器的前置, 后置, 完成方法就一目了然了. 其实Spring Mvc就是将拦截器统一放到了拦截器数组中, 然后在调用真正的处理请求方法之前和之后正序或者倒序遍历拦截器, 同时调用拦截器的相应的方法. 最后不管是否正常结束这个流程还是出异常都会从成功的最后一个拦截器开始逆序调用afterCompletion方法

Filter 和 Inteceptor 调用流程

总结:

  1. 从以上分析可以看到过滤器和拦截器实现的方式的不同. Filter是利用了方法的调用(入栈出栈)完成整个流程, 而Interceptor是利用了for循环完成了整个流程.
  2. Filter的实现比较占用栈空间, 在Filter多的情况下可能会有栈溢出的风险存在.
  3. Interceptor的实现逻辑更加的清晰简单
  4. Filter组件更加的通用, 只要支持java servlet的容器都可以使用, 而Interceptor必须依赖于Spring
  5. Filter的优先级是高于Interceptor, 即请求是先到Filter再到Interceptor的, 因为Interceptor的实现主体还是一个servlet
  6. Filter 不能使用 spring