✅ 引言
在 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 拦截器有三个核心方法,对应请求处理的三个阶段:
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 实现步骤
第一步:创建拦截器
@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;
}
}
第二步:注册拦截器
@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
@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无效 |
📌 四、高级用法:使用拦截器记录接口性能
@Component
public class PerformanceInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(PerformanceInterceptor.class);
// 使用 ThreadLocal 存储开始时间
private static final ThreadLocal<Long> 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。
@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 请求的“调度权”。