0%

拦截器

Spring MVC 拦截器详解:从原理到实践

拦截器(Interceptor)是 Spring MVC 提供的核心功能之一,用于在请求处理的不同阶段进行拦截并执行自定义逻辑(如权限校验、日志记录、性能监控等)。它与 Servlet 过滤器(Filter)类似,但更贴合 Spring MVC 的生命周期,使用更灵活。从 “拦截器与过滤器的区别→拦截器的使用→执行原理→实战场景” 全面解析 Spring MVC 拦截器。

拦截器与过滤器的核心区别

虽然拦截器和过滤器都能实现请求拦截,但两者在技术归属、执行时机、拦截范围等方面有本质区别:

特性 拦截器(Interceptor) 过滤器(Filter)
技术归属 Spring MVC 框架提供 Servlet 规范提供(Java EE 原生)
执行时机 在 DispatcherServlet 内部执行 在 DispatcherServlet 之前执行
拦截范围 仅拦截 Spring MVC 管理的请求(如 @RequestMapping 标注的方法) 拦截所有进入 Web 容器的请求(包括静态资源、JSP 等)
底层实现 基于 Java 反射机制 基于函数回调
生命周期管理 由 Spring 容器管理,可注入 Spring Bean 由 Web 容器管理,无法直接使用 Spring 资源
方法粒度 可精确到 Controller 方法级别 仅能到 URL 级别

拦截器的核心接口与方法

Spring MVC 拦截器通过 HandlerInterceptor 接口定义核心行为,包含三个关键方法,分别对应请求处理的不同阶段:

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

/**
* 1. 预处理:目标 Controller 方法执行前调用
* @param request 请求对象
* @param response 响应对象
* @param handler 目标处理器(通常是 Controller 方法)
* @return true:继续执行(后续拦截器或目标方法);false:中断执行(直接返回响应)
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

/**
* 2. 后处理:目标 Controller 方法执行后、视图渲染前调用
* @param modelAndView 目标方法返回的 ModelAndView 对象(可修改视图或模型数据)
*/
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;

/**
* 3. 完成处理:视图渲染后调用(无论是否发生异常)
* @param ex 目标方法执行过程中抛出的异常(无异常则为 null)
*/
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
}

方法作用与典型场景

方法名 执行阶段 典型应用场景
preHandle 目标方法执行前 权限校验(如未登录则拦截)、日志记录(请求开始时间)、参数校验
postHandle 目标方法执行后,视图渲染前 修改模型数据(如统一添加全局参数)、替换视图
afterCompletion 视图渲染后 资源释放(如关闭文件流)、日志记录(请求结束时间,计算耗时)

拦截器的使用步骤

使用拦截器需经过 “实现接口→配置拦截规则” 两步,以下是详细流程:

步骤 1:实现拦截器接口

方式 1:直接实现 HandlerInterceptor 接口(推荐)

需重写所有三个方法(无需实现的方法可留空):

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
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginInterceptor implements HandlerInterceptor {

// 预处理:检查用户是否登录
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 从 Session 中获取用户信息
Object user = request.getSession().getAttribute("loginUser");

// 2. 未登录:重定向到登录页,返回 false 中断执行
if (user == null) {
response.sendRedirect(request.getContextPath() + "/login");
return false;
}

// 3. 已登录:继续执行后续流程
return true;
}

// 后处理:可修改视图或模型(本例无需实现)
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 示例:统一添加当前时间到模型
if (modelAndView != null) {
modelAndView.addObject("currentTime", System.currentTimeMillis());
}
}

// 完成处理:释放资源(本例无需实现)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 示例:记录请求处理耗时
Long startTime = (Long) request.getAttribute("startTime");
if (startTime != null) {
long cost = System.currentTimeMillis() - startTime;
System.out.println("请求 " + request.getRequestURI() + " 耗时:" + cost + "ms");
}
}
}
方式 2:继承 HandlerInterceptorAdapter(已过时,不推荐)

Spring 5.3 后该类已标记为过时,建议直接实现接口:

1
2
3
4
5
6
7
8
// 过时写法,不推荐使用
public class OldInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 仅实现需要的方法
return true;
}
}

步骤 2:配置拦截器(XML 或 Java 配置)

方式 1:XML 配置(传统项目)

通过 <mvc:interceptors> 标签配置拦截器及拦截规则:

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
<!-- Spring MVC 配置文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 1. 全局拦截器:拦截所有请求(不推荐,通常需要排除静态资源) -->
<!-- <bean class="com.example.interceptor.GlobalInterceptor"/> -->

<!-- 2. 带拦截规则的拦截器(推荐) -->
<mvc:interceptor>
<!-- 拦截路径:/** 表示所有请求(包括子路径) -->
<mvc:mapping path="/**"/>
<!-- 排除路径:不拦截登录页和静态资源 -->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/static/**"/>
<!-- 拦截器实现类 -->
<bean class="com.example.interceptor.LoginInterceptor"/>
</mvc:interceptor>

<!-- 可配置多个拦截器,按配置顺序执行 preHandle 方法 -->
<mvc:interceptor>
<mvc:mapping path="/admin/**"/> <!-- 仅拦截 admin 路径下的请求 -->
<bean class="com.example.interceptor.AdminInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
方式 2:Java 配置(现代项目,推荐)

通过 WebMvcConfigureraddInterceptors 方法配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration // 标记为配置类
public class WebMvcConfig implements WebMvcConfigurer {

// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 1. 注册登录拦截器
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login", "/static/**"); // 排除路径

// 2. 注册管理员拦截器(仅拦截 /admin 路径)
registry.addInterceptor(new AdminInterceptor())
.addPathPatterns("/admin/**");

// 注意:多个拦截器的注册顺序即 preHandle 执行顺序
}
}

拦截器的执行原理(结合源码)

拦截器的执行由 DispatcherServlet 主导,核心逻辑在 doDispatch 方法中。当请求到达 DispatcherServlet 后,会先通过 HandlerMapping 获取 HandlerExecutionChain(包含目标处理器和拦截器列表),再按顺序调用拦截器方法。

1. preHandle 方法:目标方法执行前调用(正序执行)

DispatcherServlet 在调用目标方法前,通过 HandlerExecutionChainapplyPreHandle 方法执行所有拦截器的 preHandle

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
// DispatcherServlet 核心方法片段
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
try {
// ... 省略其他逻辑 ...

// 1. 获取 HandlerExecutionChain(包含拦截器)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

// 2. 执行所有拦截器的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return; // 若有拦截器返回 false,直接结束
}

// 3. 执行目标 Controller 方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// ... 后续逻辑 ...
}
}

// HandlerExecutionChain 的 applyPreHandle 方法
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];
if (!interceptor.preHandle(request, response, this.handler)) {
// 若当前拦截器返回 false,触发已执行拦截器的 afterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
// 记录最后一个成功执行 preHandle 的拦截器索引
this.interceptorIndex = i;
}
}
return true;
}

关键逻辑

  • 拦截器按配置顺序(正序)执行 preHandle
  • 若某个拦截器返回 false,则触发 “已成功执行 preHandle 的拦截器” 的 afterCompletion 方法(反序),并终止后续流程。

2. postHandle 方法:目标方法执行后调用(反序执行)

目标方法执行完成后,DispatcherServlet 调用 HandlerExecutionChainapplyPostHandle 方法执行 postHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// DispatcherServlet 核心方法片段
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ... 省略 preHandle 和目标方法执行逻辑 ...

// 执行所有拦截器的 postHandle 方法
if (mv != null && !mv.wasCleared()) {
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}

// HandlerExecutionChain 的 applyPostHandle 方法
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);
}
}
}

关键逻辑

  • 拦截器按配置的反序执行 postHandle
  • 仅当所有拦截器的 preHandle 都返回 true 时,才会执行 postHandle

3. afterCompletion 方法:视图渲染后调用(反序执行)

无论请求处理是否发生异常,afterCompletion 都会在视图渲染后执行:

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
// DispatcherServlet 核心方法片段
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
try {
// ... 省略其他逻辑 ...
} catch (Exception ex) {
// 发生异常时,触发 afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} finally {
// 正常结束时,触发 afterCompletion
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(processedRequest, response, null);
}
}
}

// HandlerExecutionChain 的 triggerAfterCompletion 方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 从最后一个成功执行 preHandle 的拦截器开始,反序执行 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("拦截器 afterCompletion 执行异常", ex2);
}
}
}
}

关键逻辑

  • 仅执行 “preHandle 成功返回 true” 的拦截器的 afterCompletion
  • preHandle 执行顺序的反序执行(与 postHandle 顺序一致)。

多个拦截器的执行顺序

当配置多个拦截器时,执行顺序遵循 “preHandle 正序,postHandle/afterCompletion 反序” 的规则。

示例:两个拦截器的执行流程

假设配置两个拦截器 InterceptorAInterceptorB(A 在前,B 在后):

1
2
3
// 配置顺序:InterceptorA → InterceptorB
registry.addInterceptor(new InterceptorA()).addPathPatterns("/**");
registry.addInterceptor(new InterceptorB()).addPathPatterns("/**");

执行顺序如下:

1
2
3
4
5
6
7
8
1. InterceptorA.preHandle() → 返回 true
2. InterceptorB.preHandle() → 返回 true
3. 执行目标 Controller 方法
4. InterceptorB.postHandle()
5. InterceptorA.postHandle()
6. 视图渲染
7. InterceptorB.afterCompletion()
8. InterceptorA.afterCompletion()

特殊情况:若 InterceptorB.preHandle() 返回 false

1
2
3
1. InterceptorA.preHandle() → 返回 true
2. InterceptorB.preHandle() → 返回 false
3. InterceptorA.afterCompletion() → 仅执行已成功 preHandle 的拦截器

异步请求中的拦截器

对于异步请求(如返回 CallableDeferredResult),Spring MVC 提供 AsyncHandlerInterceptor 接口(继承 HandlerInterceptor),新增 afterConcurrentHandlingStarted 方法:

1
2
3
4
5
6
7
8
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
/**
* 异步请求开始时调用(此时主线程已释放)
* 注意:异步请求中不会执行 postHandle 和 afterCompletion
*/
default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
}
}

执行逻辑

  • 异步请求处理时,preHandle 正常执行;
  • 主线程会跳过 postHandleafterCompletion,直接调用 afterConcurrentHandlingStarted
  • 异步任务完成后,若需要后续处理,需通过 AsyncWebRequest 注册回调。

拦截器的典型应用场景

  1. 权限校验:在 preHandle 中检查用户是否登录或拥有操作权限(如示例中的 LoginInterceptor);
  2. 日志记录:在 preHandle 记录请求开始时间,在 afterCompletion 记录结束时间,计算接口耗时;
  3. 参数统一处理:在 preHandle 中对请求参数进行加密 / 解密、XSS 过滤等;
  4. 全局数据添加:在 postHandle 中向 ModelAndView 添加全局参数(如当前登录用户信息、系统配置);
  5. 资源释放:在 afterCompletion 中关闭文件流、释放数据库连接等。

总结

Spring MVC 拦截器是基于 HandlerInterceptor 接口的请求拦截机制,核心特点如下:

  1. 三个核心方法preHandle(前置处理)、postHandle(后置处理)、afterCompletion(完成处理),分别对应请求处理的不同阶段;
  2. 执行顺序:多个拦截器时,preHandle 正序执行,postHandleafterCompletion 反序执行;
  3. 与过滤器的区别:拦截器属于 Spring MVC,在 DispatcherServlet 内部执行,可精确到方法级别,且能使用 Spring 资源;
  4. 灵活配置:支持 XML 和 Java 两种配置方式,可通过 addPathPatternsexcludePathPatterns 精确控制拦截范围

欢迎关注我的其它发布渠道