Blog Detail

  • Spring Boot 拦截器详解

    ✅ 引言

    在 Spring Boot 开发中,拦截器(Interceptor) 是一个强大而灵活的工具,常用于:

    ✅ 用户登录状态校验

    ✅ 接口权限控制

    ✅ 请求日志记录

    ✅ 接口性能监控

    ✅ 全局异常处理前预处理

    它就像一道"关卡",在请求到达控制器之前、之后,甚至视图渲染完成后,都可以进行干预和处理。

    本文将带你深入理解 Spring Boot 拦截器的工作原理,结合代码示例 与生产实践,助你掌握这一核心技能。

    📌 一、拦截器是什么?它和过滤器有什么区别?

    1.1 拦截器(Interceptor) vs 过滤器(Filter)

    特性

    拦截器(Interceptor)

    过滤器(Filter)

    所属框架

    Spring MVC

    Servlet

    拦截范围

    只拦截 Controller 请求

    拦截所有 Web 请求(包括静态资源)

    依赖容器

    依赖 Spring 容器,可注入 Bean

    不依赖 Spring,无法直接使用 @Autowired

    执行时机

    在 DispatcherServlet 调用 Handler 时触发

    在请求进入 Servlet 容器时触发

    📌 简单说:

    Filter 是"大门守卫",所有进出的都要检查。

    Interceptor 是"办公室门卫",只管进入办公区(Controller)的人。

    📌 二、拦截器的生命周期(三阶段)

    Spring Boot 拦截器有三个核心方法,对应请求处理的三个阶段:

    java

    复制代码

    public class MyInterceptor implements HandlerInterceptor {

    // 请求到达 Controller 前调用

    @Override

    public boolean preHandle(HttpServletRequest request,

    HttpServletResponse response,

    Object handler) throws Exception {

    System.out.println("preHandle: 请求预处理");

    // 返回 true:放行,继续执行

    // 返回 false:中断,不再执行后续操作

    return true;

    }

    //Controller 方法执行后,视图渲染前调用

    @Override

    public void postHandle(HttpServletRequest request,

    HttpServletResponse response,

    Object handler,

    ModelAndView modelAndView) throws Exception {

    System.out.println("postHandle: 后处理(可修改ModelAndView)");

    }

    //整个请求完成后调用(包括视图渲染)

    @Override

    public void afterCompletion(HttpServletRequest request,

    HttpServletResponse response,

    Object handler,

    Exception ex) throws Exception {

    System.out.println("🎉 afterCompletion: 请求完成(可用于资源清理)");

    }

    }

    🔄 执行流程图

    📌 三、实战:手把手实现一个登录拦截器

    3.1 需求描述

    所有 /api/** 接口需要登录才能访问。

    登录接口 /api/login 放行。

    未登录用户访问受保护接口时,返回 401 状态码。

    3.2 实现步骤

    第一步:创建拦截器

    java

    复制代码

    @Component

    public class LoginInterceptor implements HandlerInterceptor {

    private static final String LOGIN_URI = "/api/login";

    private static final String TOKEN_HEADER = "Authorization";

    @Override

    public boolean preHandle(HttpServletRequest request,

    HttpServletResponse response,

    Object handler) throws Exception {

    // 1. 放行登录接口

    if (LOGIN_URI.equals(request.getRequestURI())) {

    return true;

    }

    // 2. 检查请求头中的 token

    String token = request.getHeader(TOKEN_HEADER);

    if (token == null || token.isEmpty()) {

    response.setStatus(401);

    response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}");

    return false;

    }

    // 3. 校验 token(简化版:只判断非空)

    // 实际项目中应调用 AuthService 校验 JWT 或查询 Redis

    if (!"valid-token-123".equals(token)) {

    response.setStatus(401);

    response.getWriter().write("{\"code\":401,\"msg\":\"token无效\"}");

    return false;

    }

    // 4. 登录校验通过,放行

    return true;

    }

    }

    第二步:注册拦截器

    java

    复制代码

    @Configuration

    public class WebConfig implements WebMvcConfigurer {

    @Autowired

    private LoginInterceptor loginInterceptor;

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

    registry.addInterceptor(loginInterceptor)

    .addPathPatterns("/api/**") // 拦截所有 /api 开头的请求

    .excludePathPatterns("/api/login", "/api/public/**"); // 排除登录和公共接口

    }

    }

    第三步:测试 Controller

    java

    复制代码

    @RestController

    @RequestMapping("/api")

    public class TestController {

    @GetMapping("/login")

    public String login() {

    return "{\"token\":\"valid-token-123\"}";

    }

    @GetMapping("/user/info")

    public String userInfo() {

    return "{\"id\":1, \"name\":\"张三\"}";

    }

    @GetMapping("/admin/dashboard")

    public String admin() {

    return "{\"data\":\"admin only\"}";

    }

    }

    ✅ 测试结果

    请求

    Header[Authorization]

    结果

    GET /api/login

    -

    ✅ 返回 token

    GET /api/user/info

    valid-token-123

    ✅ 返回用户信息

    GET /api/user/info

    (空)

    ❌ 401 未登录

    GET /api/admin/dashboard

    invalid-token

    ❌ 401 token无效

    📌 四、高级用法:使用拦截器记录接口性能

    java

    复制代码

    @Component

    public class PerformanceInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(PerformanceInterceptor.class);

    // 使用 ThreadLocal 存储开始时间

    private static final ThreadLocal startTimeThreadLocal = new ThreadLocal<>();

    @Override

    public boolean preHandle(HttpServletRequest request,

    HttpServletResponse response,

    Object handler) throws Exception {

    long startTime = System.currentTimeMillis();

    startTimeThreadLocal.set(startTime);

    return true;

    }

    @Override

    public void afterCompletion(HttpServletRequest request,

    HttpServletResponse response,

    Object handler,

    Exception ex) throws Exception {

    long startTime = startTimeThreadLocal.get();

    long endTime = System.currentTimeMillis();

    long duration = endTime - startTime;

    // 记录耗时超过 100ms 的慢请求

    if (duration > 100) {

    log.warn("⚠️ 慢请求: {} {} | 耗时: {}ms | Status: {}",

    request.getMethod(),

    request.getRequestURI(),

    duration,

    response.getStatus());

    }

    // 清理 ThreadLocal,防止内存泄漏

    startTimeThreadLocal.remove();

    }

    }

    📌 注意 :使用 ThreadLocal 后,必须在 afterCompletion 中调用 remove(),否则可能导致内存泄漏。

    📌 五、拦截器的局限性与替代方案

    5.1 拦截器无法拦截的场景

    异步请求(@Async)

    WebSocket 请求

    文件上传的 Multipart 解析阶段

    5.2 替代方案:使用 AOP(面向切面编程)

    对于更复杂的逻辑(如参数校验、缓存、重试),推荐使用 Spring AOP。

    java

    复制代码

    @Aspect

    @Component

    public class ServiceLogAspect {

    @Around("@annotation(com.example.annotation.LogExecution)")

    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {

    long start = System.currentTimeMillis();

    Object result = joinPoint.proceed();

    long duration = System.currentTimeMillis() - start;

    System.out.println("方法 " + joinPoint.getSignature() + " 执行耗时: " + duration + "ms");

    return result;

    }

    }

    📌 六、生产环境最佳实践

    实践

    说明

    ✅ 使用 @Component + @Configuration 注册

    避免硬编码

    ✅ 合理设置 addPathPatterns 和 excludePathPatterns

    避免误拦截静态资源

    ✅ preHandle 中避免耗时操作

    否则会拖慢所有请求

    ✅ ThreadLocal 用完必须 remove()

    防止内存泄漏

    ✅ 拦截器中不要抛异常

    应通过 response 返回错误码

    ✅ 多个拦截器注意顺序

    先注册的先执行 preHandle,后执行 afterCompletion

    ✅ 总结

    方法

    适用场景

    preHandle

    权限校验、日志记录、性能监控起点

    postHandle

    修改 Model、添加 Header

    afterCompletion

    资源清理、性能监控终点、异常处理

    💡 一句话总结 :

    拦截器是 Spring Boot 中控制请求流程的"闸门",掌握它,你就掌握了 Web 请求的"调度权"。

    📚 推荐

    Spring 官方文档 - Interceptors