Spring MVC 拦截器 Interceptor 详解:从执行流程到实战应用
一、拦截器是什么
拦截器(Interceptor)是 Spring MVC 提供的一种机制,用于在请求到达 Controller 之前、Controller 执行之后、请求完成之后插入自定义逻辑。
它和 Filter 很像,但归属不同:
| 对比 | Filter | Interceptor |
|---|---|---|
| 规范 | Servlet 规范 | Spring MVC 规范 |
| 容器 | Tomcat 等 Servlet 容器管理 | Spring 容器管理 |
| 作用范围 | 所有请求,包括静态资源 | 只作用于 DispatcherServlet 处理的请求 |
| 能否获取 Spring Bean | 默认不能,需额外处理 | 可以直接注入 |
| 典型用途 | 编码、跨域、安全过滤 | 登录校验、权限校验、日志记录、性能监控 |
一句话概括:Filter 是 Servlet 层的守门员,Interceptor 是 Spring MVC 层的守门员。
二、拦截器的核心接口
实现拦截器有两种方式。
1. 实现 HandlerInterceptor 接口
java
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable Exception ex) throws Exception {
}
}
三个方法对应请求的三个阶段。Spring 5.3 之后接口方法都有默认实现,可以只重写需要的方法。
2. 继承 HandlerInterceptorAdapter(已过时)
Spring 5.3 之前常用 HandlerInterceptorAdapter,它提供了空实现。Spring 5.3 之后接口本身有默认方法,这个类已标记为过时,不再需要。
三、三个方法的执行时机
请求到达 DispatcherServlet
→ 匹配 Handler
→ 按顺序执行拦截器的 preHandle
├── 返回 true → 继续下一个拦截器或 Controller
└── 返回 false → 中断,后续拦截器和 Controller 不执行
→ Controller 执行
→ 逆序执行拦截器的 postHandle
→ 视图渲染
→ 逆序执行拦截器的 afterCompletion
1. preHandle:Controller 之前
java
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle: " + request.getRequestURI());
return true;
}
| 返回值 | 效果 |
|---|---|
true |
放行,继续后续流程 |
false |
中断,Controller 和后续拦截器不执行 |
preHandle 是最常用的方法,登录校验、权限校验、限流都在这里做。
注意 :如果 preHandle 返回 false,当前拦截器的 postHandle 和 afterCompletion 都不会执行,但之前已经执行过 preHandle 的拦截器 ,它们的 afterCompletion 仍会执行。
2. postHandle:Controller 之后、视图渲染之前
java
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
- Controller 正常执行后调用。
- 可以修改
ModelAndView,比如往模型里补充公共数据。 - Controller 抛出异常时,
postHandle不执行。
3. afterCompletion:视图渲染之后
java
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion");
}
- 整个请求完成后调用,类似
finally。 - 无论 Controller 是否抛异常都会执行。
- 适合清理资源,比如移除
ThreadLocal、关闭流。
四、多个拦截器的执行顺序
假设有两个拦截器 A 和 B,按 A、B 顺序注册:
A.preHandle
B.preHandle
Controller
B.postHandle
A.postHandle
视图渲染
B.afterCompletion
A.afterCompletion
规律:
preHandle按注册顺序执行。postHandle和afterCompletion按注册逆序执行。- 如果 B.preHandle 返回 false,则 A.afterCompletion 会执行,B 的 postHandle 和 afterCompletion 都不会执行。
用一张图表示:
A.preHandle → B.preHandle → Controller → B.postHandle → A.postHandle → 渲染 → B.afterCompletion → A.afterCompletion
五、注册拦截器
1. Spring Boot / Java Config 方式
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register", "/static/**");
}
}
| 方法 | 作用 |
|---|---|
addPathPatterns |
拦截哪些路径 |
excludePathPatterns |
排除哪些路径 |
order |
多个拦截器的顺序 |
2. 传统 Spring MVC XML 方式
xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/static/**"/>
<bean class="com.xie.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
六、完整示例:登录拦截器
1. 拦截器实现
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 放行 OPTIONS 预检请求
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
return true;
}
// 从 Session 中取登录用户
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
// 未登录,重定向到登录页
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
return true;
}
}
2. 注册
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/login",
"/register",
"/static/**",
"/error"
);
}
}
3. 执行流程
用户访问 /user/profile
→ LoginInterceptor.preHandle
→ Session 中没有 loginUser
→ response.sendRedirect("/login")
→ 返回 false
→ Controller 不执行
→ 浏览器跳转到登录页
七、使用 ThreadLocal 传递用户信息
拦截器中解析出用户信息后,可以存入 ThreadLocal,供后续 Controller 和 Service 直接使用。
1. 上下文工具类
java
public class UserContext {
private static final ThreadLocal<Long> USER_ID = new ThreadLocal<>();
public static void setUserId(Long userId) {
USER_ID.set(userId);
}
public static Long getUserId() {
return USER_ID.get();
}
public static void clear() {
USER_ID.remove();
}
}
2. 拦截器中设置
java
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
User user = (User) loginUser;
UserContext.setUserId(user.getId());
return true;
}
3. 请求完成后清理
java
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
UserContext.clear();
}
必须清理。Tomcat 的线程是复用的,不清理会导致下一次请求拿到上一次的用户信息,产生严重的串数据问题。
八、拦截器的常见用途
| 用途 | 实现方法 |
|---|---|
| 登录校验 | preHandle 判断 Session 或 Token |
| 权限校验 | preHandle 判断用户角色 |
| 日志记录 | preHandle 记录开始时间,afterCompletion 记录耗时 |
| 链路追踪 | preHandle 生成 traceId 存入 MDC |
| 限流 | preHandle 计数或令牌桶判断 |
| 公共数据注入 | postHandle 往 ModelAndView 补充数据 |
| 资源清理 | afterCompletion 清理 ThreadLocal |
九、常见问题
1. 拦截器不生效
- 拦截器没有注册到
WebMvcConfigurer。 - 路径匹配写错,
addPathPatterns没覆盖到目标路径。 - 拦截器所在类没有被 Spring 管理,
@Autowired注入为null。 - 请求走的是静态资源,被
excludePathPatterns排除了。
2. 拦截器中注入的 Bean 为 null
java
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService; // 可能为 null
}
原因:拦截器没有注册为 Spring Bean。需要加 @Component,或者用 new 创建时手动注入。
推荐方式:
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
}
java
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor);
}
3. postHandle 不执行
Controller 抛出异常时,postHandle 不执行。需要确保逻辑在 afterCompletion 中执行。
4. afterCompletion 没有执行
如果 preHandle 返回 false,当前拦截器的 afterCompletion 不会执行。只有 preHandle 返回过 true 的拦截器,才会执行 afterCompletion。
5. 静态资源被拦截
java
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/static/**", "/css/**", "/js/**");
静态资源不需要登录校验,必须排除,否则页面加载 CSS、JS 时会重定向到登录页。
6. 拦截器顺序不对
多个拦截器时,preHandle 按注册顺序,postHandle 和 afterCompletion 逆序。需要明确顺序时,通过 registry.addInterceptor(a).order(1) 指定。
7. 拦截器执行了但 Controller 没执行
preHandle 返回了 false。检查拦截器里的判断逻辑。
十、和 Filter、AOP 的区别
| 对比 | Filter | Interceptor | AOP |
|---|---|---|---|
| 层级 | Servlet | Spring MVC | 方法 |
| 作用对象 | 所有请求 | Controller 请求 | 任意方法 |
| 能否拿到 Spring Bean | 默认不能 | 能 | 能 |
| 能否拿到请求响应 | 能 | 能 | 默认不能 |
| 典型用途 | 编码、跨域 | 登录、权限、日志 | 事务、缓存、监控 |
选择建议:
- 需要在 DispatcherServlet 之前处理,用 Filter。
- 需要 Controller 上下文和 Spring Bean,用 Interceptor。
- 需要方法级别的增强,用 AOP。
十一、记忆方法
1. 一句话口诀
preHandle 拦在前,postHandle 在中间,
afterCompletion 收尾,三个方法管全程。
2. 三个方法对应时机
preHandle → Controller 之前
postHandle → Controller 之后、渲染之前
afterCompletion → 渲染之后、请求结束
3. 返回值口诀
preHandle 返回 true 放行,false 中断;
postHandle 和 afterCompletion 没有返回值。
4. 多个拦截器顺序口诀
preHandle 正序,
postHandle 和 afterCompletion 逆序,
像栈一样,先进后出。
5. 注册口诀
addInterceptor 注册,
addPathPatterns 拦截,
excludePathPatterns 排除。
6. ThreadLocal 口诀
preHandle 存进去,
afterCompletion 清掉,
不清理线程复用会串数据。
7. 和 Filter 区分
Filter 管所有请求,
Interceptor 只管 Controller;
Filter 在容器层,Interceptor 在 Spring MVC 层。
8. 排查口诀
不生效 → 查注册和路径
Bean 为 null → 查是否 @Component
postHandle 没执行 → 查是不是抛异常了
静态资源被拦 → 查 excludePathPatterns
十二、总结
| 维度 | 说明 |
|---|---|
| 本质 | Spring MVC 层的请求拦截机制 |
| 核心接口 | HandlerInterceptor |
| 三个方法 | preHandle、postHandle、afterCompletion |
| 执行顺序 | preHandle 正序,postHandle 和 afterCompletion 逆序 |
| 注册方式 | WebMvcConfigurer.addInterceptors 或 XML |
| 常用用途 | 登录校验、权限校验、日志、链路追踪、限流 |
| 和 Filter 区别 | Filter 在 Servlet 层,Interceptor 在 Spring MVC 层 |
| 和 AOP 区别 | Interceptor 处理请求,AOP 处理方法 |
| 常见问题 | 不生效查注册、Bean 为 null 查 @Component、ThreadLocal 必须清理 |
拦截器的核心是"三个方法对应请求的三个阶段 ":preHandle 在 Controller 前拦截,postHandle 在 Controller 后补充数据,afterCompletion 在请求结束后清理资源。记住"preHandle 正序、postHandle 和 afterCompletion 逆序",再记住"preHandle 返回 false 会中断,但已放行的拦截器仍会执行 afterCompletion",这个机制的边界就清楚了。实际开发中最常用的是 preHandle 做登录和权限校验,配合 ThreadLocal 传递用户信息,并在 afterCompletion 中清理,避免线程复用导致数据串号。