Spring MVC 拦截器 Interceptor 详解:从执行流程到实战应用

本文深入解析Spring MVC拦截器(Interceptor)的核心机制,涵盖HandlerInterceptor接口的三个关键方法(preHandle、postHandle、afterCompletion)执行时机与返回值影响,多个拦截器的执行顺序规则,Java Config与XML两种注册方式,登录校验实战示例,ThreadLocal跨层传递用户信息及清理要点,并对比Filter与AOP的适用场景。同时总结常见问题如拦截器不生效、Bean注入为null、静态资源误拦截等排查方案。

Spring MVC 拦截器 Interceptor 详解:从执行流程到实战应用

一、拦截器是什么

拦截器(Interceptor)是 Spring MVC 提供的一种机制,用于在请求到达 Controller 之前、Controller 执行之后、请求完成之后插入自定义逻辑。

它和 Filter 很像,但归属不同:

对比FilterInterceptor
规范Servlet 规范Spring MVC 规范
容器Tomcat 等 Servlet 容器管理Spring 容器管理
作用范围所有请求,包括静态资源只作用于 DispatcherServlet 处理的请求
能否获取 Spring Bean默认不能,需额外处理可以直接注入
典型用途编码、跨域、安全过滤登录校验、权限校验、日志记录、性能监控

一句话概括:Filter 是 Servlet 层的守门员,Interceptor 是 Spring MVC 层的守门员。

二、拦截器的核心接口

实现拦截器有两种方式。

1. 实现 HandlerInterceptor 接口

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 之前

@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 之后、视图渲染之前

@Override
public void postHandle(HttpServletRequest request,
                       HttpServletResponse response,
                       Object handler,
                       ModelAndView modelAndView) throws Exception {
    System.out.println("postHandle");
}
  • Controller 正常执行后调用。
  • 可以修改 ModelAndView,比如往模型里补充公共数据。
  • Controller 抛出异常时,postHandle 不执行。

3. afterCompletion:视图渲染之后

@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 方式

@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 方式

<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. 拦截器实现

@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. 注册

@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. 上下文工具类

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. 拦截器中设置

@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. 请求完成后清理

@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

public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;  // 可能为 null
}

原因:拦截器没有注册为 Spring Bean。需要加 @Component,或者用 new 创建时手动注入。

推荐方式:

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService;
}
@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. 静态资源被拦截

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 的区别

对比FilterInterceptorAOP
层级ServletSpring 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 中清理,避免线程复用导致数据串号。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

步行cgn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值