Spring MVC 拦截器(Interceptor)是 Spring Web MVC 框架提供的一种面向切面编程的轻量级机制,用于在处理器方法(Controller)调用前、调用后以及视图渲染完成后执行自定义逻辑,它的配置方式清晰、执行顺序可控、与业务解耦,是构建权限校验、日志记录、性能监控、请求审计等横切关注点的首选方案,相比 Filter(过滤器),拦截器深度集成 Spring 容器,可以注入任何 Spring Bean,因此在实际企业级项目中应用更广泛、更灵活。
拦截器的三种配置方式(基于 Spring MVC)
XML 方式最传统,适合老项目
在 spring-mvc.xml 或 dispatcher-servlet.xml 中使用 <mvc:interceptors>
<mvc:interceptors>
<!-- 全局拦截器:拦截所有请求 -->
<bean class="com.example.interceptor.AuthInterceptor"/>
<!-- 路径拦截器:只拦截特定路径 -->
<mvc:interceptor>
<mvc:mapping path="/api/"/>
<mvc:exclude-mapping path="/api/login"/>
<bean class="com.example.interceptor.ApiInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
Java Config 方式推荐,类型安全
实现 WebMvcConfigurer 接口并重写 addInterceptors 方法:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/")
.excludePathPatterns("/login", "/register", "/static/");
}
}
注解驱动 + 自定义注解精准控制,适合细粒度鉴权
通过自定义注解配合拦截器,实现方法级别的权限控制:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value();
}
在拦截器中通过 HandlerMethod 获取注解并校验,这种方式能够将权限逻辑从业务代码中完全剥离,维护性极佳。
拦截器核心方法详解(推荐直接复用的基类)
Spring MVC 提供了 HandlerInterceptor 接口,包含三个默认方法(Java 8+ 默认方法),建议继承 HandlerInterceptorAdapter(旧版)或直接实现接口:
preHandle:在 Controller 方法执行前调用。返回true继续执行,返回false则中断请求,适合做登录校验、黑名单拦截、参数预处理。postHandle:Controller 执行后、视图渲染前调用,适合修改 ModelAndView、统一添加公共数据。afterCompletion:请求完成后回调(无论是否异常),适合清理资源、记录日志、统计耗时。

重要:若 preHandle 返回 false,则后续的 postHandle 和 afterCompletion 都不会执行,且同链路上已执行过 preHandle 的拦截器的 afterCompletion 会逆序执行。
执行顺序与常见误区(干货)
拦截器的执行顺序遵循责任链模式,但存在一个关键细节:
- 若配置了多个拦截器 A、B,则执行顺序为:
A.preHandle → B.preHandle → Controller → B.postHandle → A.postHandle → 视图渲染 → B.afterCompletion → A.afterCompletion preHandle顺序执行,postHandle和afterCompletion逆序执行。
常见误区:
- 误区一:认为
postHandle一定能执行,实际上如果 Controller 抛出异常,postHandle不会执行,只有afterCompletion会执行。 - 误区二:拦截器只拦截 Controller 方法,不拦截静态资源,若需拦截静态资源,需要配置
<mvc:resources>与拦截器路径同时覆盖,或者直接使用 Filter。 - 误区三:拦截器与过滤器混淆,Filter 属于 Servlet 规范,基于容器,拦截所有请求包括静态资源;拦截器属于 Spring MVC,只针对 DispatcherServlet 路由的处理器。权限校验优先用拦截器,字符编码、CORS 等基础过滤优先用 Filter。
实战:完整登录拦截器 + 性能监控(附代码)
以下代码展示一个可复用的登录校验与性能统计拦截器,同时解决异步请求时 afterCompletion 中获取用户信息可能失效的问题(通过 HandlerMethod 和 ThreadLocal 隔离):
public class AuthAndMonitorInterceptor implements HandlerInterceptor {
private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 性能监控开始
START_TIME.set(System.currentTimeMillis());
// 身份校验逻辑
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
// 检查类/方法上的 @RequiresPermission 注解
RequiresPermission perm = hm.getMethodAnnotation(RequiresPermission.class);
if (perm != null && !checkPermission(request, perm.value())) {
response.setStatus(403);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{"code":403,"msg&qu
ot;:"无权限"}");
return false; // 中断请求,不再执行后续逻辑
}
}
// 如为登录接口或公开接口,放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
Long start = START_TIME.get();
if (start != null) {
long cost = System.currentTimeMillis() - start;
System.out.println(request.getRequestURI() + " 耗时: " + cost + "ms");
START_TIME.remove(); // 防止线程池复用导致数据错乱
}
}
private boolean checkPermission(HttpServletRequest request, String perm) {
// 从 Session 或 Token 中解析权限,这里做简化演示
return request.getSession().getAttribute("user") != null;
}
}
配置该拦截器(Java Config):
registry.addInterceptor(new AuthAndMonitorInterceptor())
.addPathPatterns("/")
.excludePathPatterns("/login", "/error", "/static/");
酷番云经验案例:高并发场景下的拦截器线程安全实践
酷番云在服务某金融级客户时遇到一个典型问题:拦截器中使用了非线程安全的成员变量来存储请求上下文,导致高并发下用户数据串号,我们当时给出的方案是结合酷番云云服务器(SSD 高性能实例)进行压测验证,最终落地了以下实践:
- 禁止在拦截器中定义可变的成员变量,必须使用
ThreadLocal或在方法栈内传递数据。 - 对于需要全局共享的只读配置(比如白名单路径列表),使用
volatile修饰并只读初始化,避免并发失效。 - 利用酷番云的弹性伸缩能力,在流量高峰前预置多台云服务器,同时利用缓存的
excludePathPatterns配置热更新通过酷番云对象存储下发配置,拦截器内定期拉取,实现免重启更新拦截规则。 - 经过压测,性能瓶颈不在拦截器,而在于数据库连接和日志同步写,于是我们将日志改为异步写入酷番云日志服务,拦截器只负责采集,显著提升吞吐量。
核心观点:拦截器配置本身简单,但企业级应用必须关注并发安全、性能监控和可观测性,建议在拦截器内只做轻量级逻辑,杜绝数据库查询、远程调用等耗时操作,如有需要可结合消息队列异步化。
性能对比与最佳实践(E-E-A-T 经验总结)
| 方式 | 性能影响 | 使用场景 | 推荐度 |
|---|---|---|---|
| XML 配置 | 无额外开销,启动加载略慢 | 老项目维护 | |
| Java Config |
无额外开销,类型安全 | 新项目、Spring Boot 项目 | |
| 自定义注解拦截 | 反射少量开销 | 细粒度权限控制 | |
| 多个拦截器链 | 每个请求多几次方法调用 | 拆分为独立职责 |
最佳实践清单:
- 将登录校验和日志监控拆分为两个独立拦截器,遵循单一职责。
- 所有拦截器路径用 覆盖大部分场景,再用
excludePathPatterns排除登录、静态资源、异常页。 - 在拦截器中不要捕获所有异常,让异常交给
@ControllerAdvice统一处理。 - 如需在
postHandle中访问用户信息,务必确保信息存储与当前线程绑定,避免异步线程穿透。
相关问答模块
问题1:Spring MVC 拦截器和 Filter 过滤器有什么区别?什么时候选拦截器?
答:两者本质不同。Filter 是 Servlet 规范的一部分,由容器管理,注册在 web.xml 或 @WebFilter 中,可以拦截一切请求(包括静态资源、JSP);而拦截器是 Spring MVC 的组件,只拦截进入 DispatcherServlet 的请求,并且可以方便地注入 Spring Bean,对于纯 HTTP 层面的处理(如字符编码、CORS、防 XSS 攻击),优先选择 Filter;对于需要与 Spring 业务上下文交互的逻辑(如权限校验、用户会话检查、controller 层日志监听),拦截器更合适,另外拦截器还可以通过 HandlerMethod 获取方法上的注解,实现非常灵活的细粒度控制,这是 Filter 做不到的。
问题2:如何在拦截器中访问 Spring 配置文件中的属性(如白名单路径)?
答:有几种推荐方式,第一,通过构造器注入:在 Java Config 中将 @Value 配置传给拦截器实例;第二,在拦截器中通过 WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext()) 获取容器并 getBean,但不推荐,耦合高;第三,使用 @Component 注册拦截器,并在拦截器内部使用 @Value 注入属性。最优做法是在 WebMvcConfig 的 addInterceptors 方法中直接构造拦截器并传入 @Value 参数,这样避免拦截器作为 Bean 被其他组件误依赖,同时保持清晰的配置入口。
public void addInterceptors(InterceptorRegistry registry) {
AuthInterceptor interceptor = new AuthInterceptor(whitelist);
registry.addInterceptor(interceptor).addPathPatterns("/");
}
互动一下:你在实际项目中踩过拦截器执行顺序的坑吗?或者对拦截器与 AOP 的结合使用有独到心得?欢迎在评论区分享你的真实案例,一起讨论更好的实践方案!
图片来源于AI模型,如侵权请联系管理员。作者:酷小编,如若转载,请注明出处:https://www.kufanyun.com/ask/728790.html

