SpringMVC-拦截器(Interceptor)

概述

SpringMVC中的拦截器(Interceptor),其功能和Servlet中的Filter过滤器相似,主要用于拦截请求并作相应的处理。比如通过它来进行权限验证,或者判断用户是否登陆等。Spring MVC拦截器是可插拔式的设计,如果需要增加一个自定义拦截器,只需要将拦截器注入到spring容器中即可。
在SpringMVC中定义一个Interceptor非常简单,主要有两种方式:

  • 第一种方式是自定义一个类并实现HandlerInterceptor接口,或者是这个类继承实现了HandlerInterceptor接口的类,比如Spring已经提供的实现了HandlerInterceptor接口的抽象类HandlerInterceptorAdapter
  • 第二种方式是自定义一个类并实现WebRequestInterceptor接口,或者是继承实现了WebRequestInterceptor的类。

本文主要讨论的是实现HandlerInterceptor接口的方式。

主要方法和流程

主要方法

HandlerInterceptor 接口中定义了三个方法,我们就是通过这三个方法来对请求进行拦截处理的。

  1. preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法
    顾名思义,该方法将在处理请求之前被调用。SpringMVC 中的 Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是 Interceptor 中的 preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。
    该方法的返回值是布尔值Boolean 类型的,当它返回为false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为true时就会继续调用下一个Interceptor 的 preHandle 方法,如果已经是最后一个Interceptor了则接下来会调用当前请求对应的Controller方法。

  2. postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法
    postHandle 方法只会在当前所属的 Interceptor 的 preHandle 方法的返回值为true,并且controller成功处理该请求之后才会被调用,如果controller处理请求时抛出异常,postHandle 不会被执行。
    postHandle 方法是在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在 postHandle 方法中对Controller处理之后的 ModelAndView 对象进行操作。
    postHandle 方法的调用顺序跟 preHandle 是相反的(倒序执行),也就是说先声明的 Interceptor 的 postHandle 方法反而会后执行。

  3. afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法
    afterCompletion 方法只会在当前所属的 Interceptor 的 preHandle 方法的返回值为true时才会被调用,并且一定会执行,即使controller处理请求时抛出了异常。
    afterCompletion 方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。
    这个方法的主要是用于做一些资源清理工作。

执行顺序

单拦截器的执行流程

只有一个拦截器MyFirstInterceptor

  • 正常的执行

    1
    2
    3
    4
    5
    MyFirstInterceptor->preHandle...
    目标方法.....
    MyFirstInterceptor->postHandle...
    页面渲染了...
    MyFirstInterceptor->afterCompletion...
  • 不正常的流程(即出现异常的情况);

    1. preHandle返回了false,拦截目标方法不会执行。

      1
      MyFirstInterceptor->preHandle...
    2. preHandle返回了true, 但目标方法出现异常,postHandle不执行,但是afterCompletion会执行。

      1
      2
      3
      MyFirstInterceptor->preHandle...
      目标方法.....
      MyFirstInterceptor->afterCompletion..

多拦截器的流程

两个拦截器:MyFirstInterceptorMySecondInterceptor,并且MyFirstInterceptor先拦截。

  • firstPrehandle返回true,secondPrehandle-true

    1
    2
    3
    4
    5
    6
    7
    8
    MyFirstInterceptor->preHandle...
    MySecondInterceptor--->preHandle...
    目标方法.....
    MySecondInterceptor--->postHandle...
    MyFirstInterceptor->postHandle...
    页面渲染了...
    MySecondInterceptor--->afterCompletion...
    MyFirstInterceptor->afterCompletion...
  • firstPrehandle返回true,secondPrehandle返回fasle

    1
    2
    3
    MyFirstInterceptor->preHandle...
    MySecondInterceptor--->preHandle...
    MyFirstInterceptor->afterCompletion...
  • firstPrehandle返回fasle,secondPrehandle返回true/false

    1
    MyFirstInterceptor->preHandle...

WebRequestInterceptor和HandlerInterceptor的不同点:

  1. WebRequestInterceptor的入参WebRequest是包装了HttpServletRequest 和HttpServletResponse的,通过WebRequest获取Request中的信息更简便。
  2. WebRequestInterceptor的preHandle是没有返回值的,说明该方法中的逻辑并不影响后续的方法执行,所以这个接口实现就是为了获取Request中的信息,或者预设一些参数供后续流程使用。
  3. HandlerInterceptor的功能更强大也更基础,可以在preHandle方法中就直接拒绝请求进入controller方法。

使用示例

下面演示一下基于SpringBoot项目,多个拦截器应该怎么配置。
先写两个拦截器:ReqThreadLocalInterceptor 和 LoginInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 在每个请求进来时,都在线程上下文ThreadContext注入一个应用的上下文,应用上下文(ApplicationContext)中可存储用户的一些基本信息等。
*/
@Component
public class ReqThreadLocalInterceptor implements HandlerInterceptor {


@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ThreadContext.setContext(new ApplicationContext());
return true;
}


@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ApplicationContext context = ThreadContext.getContext();
if (context != null) {
ThreadContext.removeContext();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 用户登陆拦截器
*/
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//验证用户登陆 todo
log.debug("验证用户登陆...");
return true;
}

}

配置 LoginInterceptor 先拦截,ReqThreadLocalInterceptor 后拦截。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class ApplicationWebMvcConfig implements WebMvcConfigurer {

@Autowired
private ReqThreadLocalInterceptor reqThreadLocalInterceptor;

@Autowired
private LoginInterceptor loginInterceptor;


@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(reqThreadLocalInterceptor).addPathPatterns("/*").order(2);
registry.addInterceptor(loginInterceptor).addPathPatterns("/*").order(1);
}

}

发起请求后,可以看到打印的日志是:

源码分析

我们知道SpringMVC的接口请求都会进入到DispatcherServletdoDispatch()方法中,如下是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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

//执行拦截器的preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

try {
//执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}

applyDefaultViewName(request, mv);
//执行拦截器的postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
//页面渲染,渲染这个方法执行完成后会触发 afterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//出异常也会触发AfterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}

preHandle的触发

preHandle的触发是在Handler的applyPreHandle()方法中。流程如下:

  1. 先获取所有的拦截器
  2. for循环拿取当前拦截器
  3. 调用preHandle
  4. preHandle返回fasle,执行afterCompletion
  5. preHandle返回true,记录了某个index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
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;
}
this.interceptorIndex = i;
}
}
return true;
}

postHandle的触发

postHandle的触发是在Handler的applyPostHandle()方法中。流程如下:

  1. 拿到所有拦截器
  2. 倒序执行所有的postHandle方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {

HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) { // i--所以会倒序执行
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}

afterCompletion的触发

在上面的doDispatch()方法中可以看到,如果目标方法执行异常,都会执行triggerAfterCompletion()方法,这个方法最终会调用handler的triggerAfterCompletion()方法,在这个方法中倒序执行拦截器的afterCompletion()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {

HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) { // i--所以会倒序执行
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}

如果目标方法正常执行,在processDispatchResult()这个方法中也会调用到handler的triggerAfterCompletion()方法。

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
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {

boolean errorView = false;

if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}

// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}

if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}

if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

拦截器和过滤器的区别

Spring的Interceptor(拦截器)与Servlet的过滤器(Filter)有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。不同的是:

  • 使用范围不同:Filter是Servlet规范规定的,只能用户Web程序中。而拦截器既可以用户Web程序,也可以用于Application、Swing程序中。
  • 规范不同,Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。
  • 使用的资源不同:同其他的代码块一样,拦截器也是一个Spring组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过IOC注入到拦截器即可;而Filter则不能。
  • 深度不同:Filter只在Servlet前后起作用。而拦截器能深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在基于Spring框架的程序中,要优先使用拦截器。

拦截器和过滤器在Web程序中的示意图:

拦截器和过滤器在Web程序中的流程图:

------ 本文完 ------