深入理解SpringMVC中的拦截器

  Spring MVC也可以使用拦截器对请求进行拦截处理。

自定义拦截器

  用户可以通过实现HandlerInterceptor接口,使用自定义的拦截器。
  下面展示如何配置自定义的拦截器:


创建拦截器类FirstInterceptor

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
public class FirstInterceptor implements HandlerInterceptor {
/**
* 该方法在目标方法之前被调用.
* 若返回值为 true, 则继续调用后续的拦截器和目标方法.
* 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
*
* 可以考虑做权限. 日志, 事务等.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("[FirstInterceptor] preHandle");
return true;
}
/**
* 调用目标方法之后, 但渲染视图之前.
* 可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[FirstInterceptor] postHandle");
}
/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[FirstInterceptor] afterCompletion");
}
}


springmvc配置文件

1
2
3
4
5
<mvc:interceptors>
<!-- 配置自定义的拦截器 -->
<bean class="com.MySpringMVC.interceptors.FirstInterceptor">
</bean>
</mvc:interceptors>


现在只要有任何页面提交请求,拦截器就会工作:

image_1b1g8etan1egn13fj18ss70l1v089.png-15.9kB

注意:即使页面请求的是不通过控制器,而是通过配置<mvc:view-controller>直接跳转到页面,拦截器也会捕获到请求。

可以通过在<mvc:interceptors>用<mvc:interceptor>配置拦截器,从而指定拦截器对哪些请求起作用,对哪些请求不起作用,例如,我们现在定义另一个拦截器类SecondInterceptor:


springmvc配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<mvc:interceptors>
<!-- 配置自定义的拦截器 FirstInterceptor -->
<bean class="com.MySpringMVC.interceptors.FirstInterceptor">
</bean>
<!-- 配置自定义的拦截器 SecondInterceptor -->
<mvc:interceptor>
<!-- 配置拦截器作用的路径 -->
<mvc:mapping path="/emps" />
<!-- 若要配置不起作用的路径,则使用 <mvc:exclude-mapping path=""/> -->
<bean class="com.MySpringMVC.interceptors.SecondInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>


创建拦截器类 SecondInterceptor

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
public class SecondInterceptor implements HandlerInterceptor {
/**
* 该方法在目标方法之前被调用.
* 若返回值为 true, 则继续调用后续的拦截器和目标方法.
* 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
*
* 可以考虑做权限. 日志, 事务等.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("[SecondInterceptor] preHandle");
return true;
}
/**
* 调用目标方法之后, 但渲染视图之前.
* 可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[SecondInterceptor] postHandle");
}
/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[SecondInterceptor] afterCompletion");
}
}


现在,如果我们在页面上请求访问的路径为emps,那么两个拦截器FirstInterceptor和SecondInterceptor都会起作用,如果请求访问emps路径以外的路径,那么只有FirstInterceptor拦截器会起作用。

拦截器方法及其执行顺序

1、单个拦截器各方法的执行顺序

  HandlerInterceptor接口定义了三个方法:

  • preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
  • postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。
  • afterCompletion():这个方法在DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。

  例如FirstInterceptor拦截器各方法的执行顺序为:

image_1b1gadk6fun63kl8um1rfn1kvsm.png-51.1kB

2、多个拦截器之间各方法的执行顺序

  当有多个拦截器同时工作时,则它们的preHandle()方法按照拦截器的配置的顺序执行,它们的postHandle()方法和afterCompletion()方法按照配置顺序的反序执行。例如我们在上面的springmvc配置文件中,先配置了FirstInterceptor,后配置了SecondInterceptor,那么当请求两个拦截器都起作用的emps路径时,拦截器的执行顺序为:
  
  image_1b1garmstfhptsineq44h1ngv13.png-20.5kB

即:

image_1b1gbv3be1vjd134g2uveju16b71p.png-65.4kB

如果当SecondInterceptor的preHandle方法返回false时,程序会怎样执行呢?
我们令SecondInterceptor的preHandle方法返回false,运行程序:

image_1b1gfcuvh2rbocd1gh7u06qvt26.png-30.6kB

即SecondInterceptor的preHandle方法返回false后,仅执行了FirstInterceptor的afterCompletion()方法,查看Spring的源码,我了解到了其工作流程是这样的:

1、当页面发送请求后,DispatcherServlet会通过其中的HandlerExecutionChain对象依次调用各拦截器的的preHandle()方法;
2、查看该HandlerExecutionChain对象,发现其中的interceptors属性记录了当前项目配置的所有拦截器:
image_1b1gg7ussuub1qfnukt9221ggo2j.png-8.6kB
其中除了我们自定义的两个拦截器以外,还有一个SpringMVC内置的ConversionServiceExposingInterceptor拦截器。此外,还有一个int型参数interceptorIndex用于记录当前时刻 preHandle()方法已经返回true的拦截器的最大下标,例如现在我们的SecondInterceptor拦截器的preHandle()方法返回了false,那么interceptorIndex参数便为1,代表拦截器ConversionServiceExposingInterceptor和FirstInterceptor的preHandle()方法返回了true:
image_1b1ggq9g91e2kgkjhie16ulhmt3d.png-9.8kB
3、当HandlerExecutionChain对象发现SecondInterceptor拦截器的preHandle()方法返回了false后,便执行triggerAfterCompletion()方法用于从下标值为interceptorIndex开始执行各拦截器的afterCompletion()方法,直到下标值减为-1为止:
image_1b1ggn7saenb1hiq1pn01s9u8km30.png-13kB

那么,我们就很好理解了为什么当SecondInterceptor拦截器的preHandle()方法返回了false后,程序运行结果为上述截图的那样了,运行流程如下:
image_1b1ggvjvfse5ce1hhqn8a1ict3q.png-154.2kB