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

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

相关推荐
文丑颜不良啊2 小时前
LeetCode1381~1400题目解析
java·leetcode
开开心心_Every2 小时前
快速隐藏程序工具自定义快捷键一键切换
java·开发语言·前端·spring·智能手机·word·intellij-idea
萧瑟余晖3 小时前
Spring Batch核心模型与快速上手详解
java·spring·batch
缓慢更新3 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能
LMT流氓兔5 小时前
第五单元——第十六课:给书店助手加上网页界面
java·开发语言·jvm·人工智能·oracle·langchain·1024程序员节
wangbing11256 小时前
平台介绍-modbus设备调试
java
开开心心就好6 小时前
音频降噪剪辑工具,音量裁剪一键处理
运维·服务器·前端·spring·ocr·音视频·1024程序员节
西索斯coding6 小时前
gpt-image-2.5-sunburst 和 gemini-3.1-flash-image 差多少?60 组项目素材的成功成本评测框架
java·linux·gpt·ai
wuminyu7 小时前
Java字节码学习总结:字节码如何加载、解析和执行
java·linux·c语言·jvm·c++