1. 拦截器在SpringBoot中的核心定位
拦截器(Interceptor)是SpringMVC框架中的核心组件之一,它在SpringBoot自动配置的加持下变得更加易用。与过滤器(Filter)不同,拦截器作用于控制器方法调用前后,可以获取到Spring的上下文环境。实际开发中,拦截器常用于实现以下场景:
- 权限验证(如JWT校验)
- 请求日志记录
- 接口耗时统计
- 全局数据预处理
- 防重复提交控制
重要区别:过滤器属于Servlet规范,而拦截器是Spring框架的产物。过滤器能处理所有HTTP请求,而拦截器只能处理进入Spring MVC处理链路的请求。
2. 拦截器实现全流程解析
2.1 基础实现步骤
- 创建拦截器类实现
HandlerInterceptor接口:
java复制public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 在控制器方法执行前调用
String token = request.getHeader("Authorization");
return JwtUtils.verify(token); // 返回false将中断请求
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) {
// 控制器方法执行后,视图渲染前调用
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// 整个请求完成后调用(视图渲染完毕)
}
}
- 注册拦截器到Spring容器:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**") // 拦截路径
.excludePathPatterns("/api/login"); // 排除路径
}
}
2.2 执行顺序深度控制
多个拦截器的执行顺序由注册顺序决定,可以通过order()方法显式控制:
java复制registry.addInterceptor(new LogInterceptor()).order(1);
registry.addInterceptor(new AuthInterceptor()).order(2);
实测发现:preHandle按注册顺序执行,postHandle和afterCompletion按逆序执行。这是典型的责任链模式实现。
3. 高级应用场景实战
3.1 注解驱动拦截方案
结合自定义注解实现更灵活的拦截控制:
- 定义权限注解:
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredRole {
String[] value();
}
- 在拦截器中解析注解:
java复制@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod)handler;
RequiredRole annotation = method.getMethodAnnotation(RequiredRole.class);
if (annotation != null) {
String[] requiredRoles = annotation.value();
// 角色校验逻辑...
}
}
return true;
}
3.2 动态路径匹配技巧
SpringBoot提供了Ant风格的路径匹配规则:
?匹配单个字符*匹配0或多个字符**匹配0或多个目录
特殊场景示例:
java复制.addPathPatterns("/admin/**") // 匹配所有/admin开头的路径
.excludePathPatterns("/**/*.html") // 排除所有HTML文件
4. 性能优化与常见陷阱
4.1 拦截器性能关键点
- 避免阻塞操作:不要在拦截器中执行耗时IO操作(如远程调用)
- 谨慎使用@Autowired:拦截器默认不是Spring Bean,直接注入会报NPE
- 路径匹配优化:复杂路径规则会增加匹配开销
4.2 典型问题排查指南
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 拦截器不生效 | 路径配置错误 | 检查addPathPatterns是否包含目标路径 |
| 注入的Bean为null | 拦截器未交给Spring管理 | 使用@Component标注拦截器类 |
| postHandle未执行 | 控制器抛出异常 | 检查控制器异常处理逻辑 |
| 静态资源被拦截 | 未正确配置排除路径 | 添加.excludePathPatterns("/static/**") |
5. 拦截器底层原理剖析
SpringBoot通过WebMvcAutoConfiguration自动配置拦截器链。核心处理流程:
- 请求进入
DispatcherServlet - 执行
HandlerExecutionChain中的拦截器preHandle - 调用实际Controller方法
- 逆序执行拦截器postHandle
- 视图渲染
- 逆序执行拦截器afterCompletion
关键源码片段(Spring 5.3.x):
java复制// AbstractHandlerMapping.java
protected HandlerExecutionChain getHandlerExecutionChain(
Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = new HandlerExecutionChain(handler);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
// 路径匹配检查
if (((MappedInterceptor) interceptor).matches(request)) {
chain.addInterceptor(interceptor);
}
}
}
return chain;
}
6. 企业级最佳实践
6.1 分布式追踪集成
在微服务架构下,建议在拦截器中处理TraceID:
java复制@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String traceId = request.getHeader("X-Trace-ID");
MDC.put("traceId", traceId != null ? traceId : UUID.randomUUID().toString());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
MDC.remove("traceId"); // 防止内存泄漏
}
6.2 防重复提交方案
基于Redis实现幂等性控制:
java复制public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws IOException {
String token = request.getHeader("Idempotent-Token");
if (StringUtils.isEmpty(token)) {
response.sendError(400, "缺少幂等令牌");
return false;
}
String key = "idempotent:" + token;
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
if (Boolean.FALSE.equals(result)) {
response.sendError(409, "请勿重复提交");
return false;
}
return true;
}
7. 拦截器扩展进阶
7.1 异步请求处理
对于@Async或DeferredResult等异步场景,需要特别处理:
java复制@Override
public void afterConcurrentHandlingStarted(
HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 异步请求开始时触发(替代postHandle/afterCompletion)
}
7.2 响应结果统一包装
结合ResponseBodyAdvice实现统一响应格式:
java复制@RestControllerAdvice
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
return new ApiResponse<>(200, "success", body);
}
}
8. 测试验证方案
8.1 单元测试示例
使用MockMvc测试拦截器:
java复制@SpringBootTest
@AutoConfigureMockMvc
class AuthInterceptorTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldBlockUnauthorizedRequest() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isForbidden());
}
@Test
void shouldAllowAuthorizedRequest() throws Exception {
mockMvc.perform(get("/api/users")
.header("Authorization", validToken))
.andExpect(status().isOk());
}
}
8.2 集成测试建议
- 验证路径匹配规则是否准确
- 测试异常流程下的拦截器行为
- 检查多拦截器组合时的执行顺序
- 验证异步请求场景下的回调触发
9. 与其他组件的协作
9.1 与过滤器的配合
典型分工方案:
- 过滤器处理:字符编码、CORS、XSS防护
- 拦截器处理:业务权限、日志记录、参数校验
执行顺序示例:
code复制HTTP请求 -> Filter#doFilter -> Interceptor#preHandle
-> Controller -> Interceptor#postHandle
-> 视图渲染 -> Interceptor#afterCompletion
-> Filter#doFilter后续逻辑
9.2 与AOP的对比选择
| 维度 | 拦截器 | AOP |
|---|---|---|
| 作用层级 | HTTP请求级别 | 方法调用级别 |
| 获取信息 | 可以获取HttpServletRequest | 只能获取方法参数 |
| 执行时机 | 控制器方法前后 | 任意Spring Bean方法调用前后 |
| 适用场景 | 需要HTTP相关信息的处理 | 与HTTP无关的业务逻辑处理 |
实际项目中,我通常会先用拦截器处理HTTP层面的通用逻辑,再用AOP处理业务层的横切关注点。
10. 生产环境经验总结
-
监控要点:
- 记录拦截器执行耗时(特别是多个拦截器链式调用时)
- 监控拦截器拒绝的请求比例
- 对频繁匹配的路径规则进行性能分析
-
配置建议:
yaml复制# application.yml示例 interceptor: auth: exclude-paths: /public/**,/healthcheck order: 1 log: include-paths: /** order: 2 -
线程安全注意:
- 避免在拦截器中使用实例变量
- 如果必须共享状态,使用ThreadLocal
- 异步场景下注意清理ThreadLocal变量
-
一个我踩过的坑:
曾经在拦截器中使用了@Value注入配置,但因为拦截器是通过new创建的,导致注入失效。正确的做法应该是:java复制@Component public class ConfigAwareInterceptor implements HandlerInterceptor { @Autowired private AppConfig config; // 注册时使用自动注入的实例 } @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private ConfigAwareInterceptor interceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor); } }
对于需要深度定制的场景,可以考虑继承WebMvcConfigurationSupport(注意这会禁用部分SpringBoot自动配置),或者更推荐的方式是实现WebMvcConfigurer接口进行扩展。
