在 Java Web 开发中,拦截机制是一个"看起来简单、实际非常关键"的能力。很多团队在项目早期只把它当成"登录校验工具",但随着系统复杂度增加,你会发现几乎所有横切逻辑都离不开它:统一鉴权、请求日志、接口限流、幂等控制、参数预处理、XSS 防护、审计追踪、性能监控、异常兜底、跨域处理等。
而在 Spring 体系中,最常见的两种拦截手段就是 Filter 和 Interceptor。
不少开发者初学时会困惑:
- Filter 和 Interceptor 有什么区别?
- 都能拦截请求,为什么要两套机制?
- 登录校验到底放哪一层更合理?
- 执行顺序是什么?异常会不会影响后续流程?
- 在 Spring Boot 项目里怎么正确配置,避免"拦不住"或"误拦截"?
本文将围绕这些核心问题,给你一份可落地的实战指南。我们不只讲概念,还会结合真实项目中的常见场景,讲清楚怎么选、怎么配、怎么避坑。读完后,你应该能建立一套完整的拦截机制设计思路。
一、先建立全局认识:请求在系统里是怎么走的?
在典型 Spring MVC Web 应用中,一个 HTTP 请求大致会经历:
- 进入 Servlet 容器(Tomcat/Jetty/Undertow)
- 先经过 Filter 链
- 进入 DispatcherServlet
- 匹配 Handler(Controller 方法)
- 经过 Interceptor 的 preHandle
- 执行 Controller
- 经过 Interceptor 的 postHandle
- 视图渲染或返回 JSON
- 触发 Interceptor 的 afterCompletion
- 响应返回客户端
这个流程决定了一个关键事实:
Filter 更靠前,更底层;Interceptor 更靠近 Spring MVC 业务执行层。
因此,它们并不是互相替代关系,而是不同层次的拦截能力。真正好的实践,是让两者各司其职。
二、Filter 是什么:Servlet 规范级别的拦截器
Filter(过滤器)来自 Servlet 规范,本质是 Web 容器层面的组件。只要是经过 Servlet 容器的请求,它就有机会处理(取决于 URL Pattern 配置)。
Filter 的典型特征
- 与 Spring MVC 无强绑定(即使不用 Spring 也能用)
- 可以处理 HttpServletRequest 和 HttpServletResponse
- 可以在请求进入业务前和响应返回前做统一处理
- 通过 FilterChain 决定是否放行
- 生命周期由容器管理(在 Spring Boot 中可由 Spring 注册和管理)
常见使用场景
- 编码设置(虽然现在多用框架默认配置)
- 跨域 CORS 处理(也可在 Spring 配置层做)
- 请求/响应包装(如可重复读 body)
- 通用日志埋点、链路 TraceId 注入
- XSS/SQL 注入等基础防护
- 非业务强相关的网关前置能力
你可以把 Filter 理解为"站在大门口的安检层",它不关心你是哪个 Controller,只关心请求整体。
三、Interceptor 是什么:Spring MVC 级别的拦截器
Interceptor(拦截器)是 Spring MVC 提供的扩展机制,通常通过实现 HandlerInterceptor 接口来定义。
Interceptor 的典型特征
- 强依赖 Spring MVC
- 拦截的是"进入 Controller 的执行过程"
- 可以拿到处理器对象(Handler),更容易做业务语义相关逻辑
- 有 preHandle、postHandle、afterCompletion 三段生命周期
- 能精细控制路径拦截与排除规则
常见使用场景
- 登录态校验、Token 校验
- 权限判断(RBAC、接口级权限)
- 用户上下文注入(如 userId、tenantId)
- 接口耗时统计、业务审计
- 幂等校验(部分场景)
- 多语言、租户信息解析等业务前置处理
你可以把 Interceptor 理解为"进入业务楼层后的前台检查",知道你要去哪个房间(哪个 Controller)。
四、Filter 与 Interceptor 核心差异对比
下面用实战视角总结关键区别:
1. 所属层级不同
- Filter:Servlet 规范层
- Interceptor:Spring MVC 框架层
2. 拦截时机不同
- Filter 更早执行
- Interceptor 在 DispatcherServlet 分发后、Controller 前后执行
3. 可感知信息不同
- Filter 不直接感知 Controller 方法信息
- Interceptor 可获取 Handler,可做更细粒度业务判断
4. 适用职责不同
- Filter 适合通用基础能力、协议层处理
- Interceptor 适合业务相关横切逻辑
5. 异常与回调语义不同
- Filter 通过 doFilter 链控制流程
- Interceptor 提供三段式回调,便于业务前后处理与收尾
实际项目中,最佳实践通常是:
基础通用能力放 Filter,业务语义拦截放 Interceptor。
五、Filter 实战:从注册到执行控制
在 Spring Boot 中使用 Filter 常见有两种方式:
- @WebFilter + @ServletComponentScan
- FilterRegistrationBean(更推荐,便于显式控制)
这里讲推荐方式。
示例:定义一个请求日志 Filter
java
@Component public class RequestLogFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; long start = System.currentTimeMillis(); String uri = req.getRequestURI(); try { chain.doFilter(request, response); } finally { long cost = System.currentTimeMillis() - start; System.out.println("[Filter] uri=" + uri + ", cost=" + cost + "ms"); } } }
配置注册与顺序
java
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean<RequestLogFilter> requestLogFilterRegistration(RequestLogFilter filter) { FilterRegistrationBean<RequestLogFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(filter); bean.addUrlPatterns("/*"); bean.setOrder(1); return bean; } }
setOrder 很关键。多个 Filter 并存时,顺序直接决定执行链路。
经验上:
- Trace/日志类靠前
- 安全/校验类次之
- 包装/响应处理类按依赖关系排序
六、Interceptor 实战:三段生命周期的正确用法
实现 HandlerInterceptor:
java
@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || token.isBlank()) { response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"msg\":\"未登录或token缺失\"}"); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // Controller 执行后、视图渲染前 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 最终收尾,适合清理 ThreadLocal } }
注册拦截器:
java
@Configuration public class WebMvcConfig implements WebMvcConfigurer { private final LoginInterceptor loginInterceptor; public WebMvcConfig(LoginInterceptor loginInterceptor) { this.loginInterceptor = loginInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/login", "/api/register", "/error"); } }
这段配置体现了 Interceptor 的实战价值:
可以对业务接口做精细路径控制,这正是登录鉴权放在 Interceptor 的重要原因之一。
七、执行顺序与调用链:面试高频、线上高危点
如果同时存在多个 Filter、多个 Interceptor,请求执行顺序通常是:
- Filter 按 order 正序进入
- Interceptor 的 preHandle 按注册顺序执行
- Controller 执行
- Interceptor 的 postHandle 逆序执行
- Interceptor 的 afterCompletion 逆序执行
- Filter 链退出(响应返回)
如果某个 Interceptor 的 preHandle 返回 false:
- 后续 Interceptor 的 preHandle 不再执行
- Controller 不会执行
- 已执行过 preHandle 的拦截器,通常仍会进入 afterCompletion(注意实际行为与异常路径)
- 需要你自己确保响应正确输出
因此,拦截失败时响应写出要规范,否则前端会收到空响应或非预期格式。
八、实战场景拆解:到底该用 Filter 还是 Interceptor?
场景1:统一注入 TraceId 到 MDC 日志上下文
推荐:Filter
原因:越早注入越好,后续所有流程(含异常)都能打到同一个 TraceId。
场景2:登录态校验、用户身份识别
推荐:Interceptor
原因:这属于业务语义层逻辑,且常依赖路径规则、注解规则、Handler 信息。
场景3:请求体防重复读取包装
推荐:Filter
原因:属于底层请求对象包装,应该在进入 MVC 前完成。
场景4:接口级权限(如 @RequiresRole)
推荐:Interceptor + 注解解析 (或直接上 Spring Security)
原因:需要感知具体 Controller 方法及其注解元信息。
场景5:跨域处理 CORS
可选:Filter 或 Spring MVC 全局配置
若系统简单,Filter 可快速兜底;若规则复杂,建议用官方配置方式统一管理。
九、与 Spring Security 的关系:不要重复造轮子
很多团队在项目中手写 Interceptor 做认证授权,这在小项目没问题;但在中大型系统中,如果涉及:
- 复杂认证方式(JWT/OAuth2/SSO)
- 细粒度权限控制
- 异常认证流程
- 安全上下文传播
建议优先考虑 Spring Security。
因为它本质上也是基于过滤器链构建的完整安全框架,能力边界远大于手写 Interceptor。
实战建议是:
- 简单登录校验:Interceptor 可用
- 正式安全体系:Spring Security 为主,Interceptor 做业务补充
十、常见坑位与排查思路
1. 拦截器不生效
常见原因:
- 没有注册到 WebMvcConfigurer
- 路径匹配错了(尤其是 /**、/* 混淆)
- 使用了 excludePathPatterns 把目标接口排除了
- 静态资源与接口路径混在一起导致误判
2. Filter 执行了两次
可能与:
- 转发(forward)/错误派发(error dispatch)有关
- Filter 映射 dispatcherType 配置有关
- Spring Boot 自动注册与手工注册重复有关
3. 在 Interceptor 中读取 body 导致 Controller 读不到
请求流默认一次性读取,解决需要用 HttpServletRequestWrapper 在 Filter 层先包装缓存。
4. ThreadLocal 泄漏
在 Interceptor preHandle 放入上下文后,必须在 afterCompletion 清理,特别是线程复用场景(Tomcat 线程池)下。
5. 异常响应格式不统一
在 Filter/Interceptor 直接写响应时,容易绕开全局异常处理器,导致返回结构不一致。
建议统一错误输出模型,或尽量抛给统一异常处理层(结合实际架构设计)。
十一、生产级最佳实践清单
- 职责分层明确:Filter 做基础通用,Interceptor 做业务语义
- 顺序可控:Filter order、Interceptor 注册顺序都要显式管理
- 路径规则收敛:统一管理白名单,避免散落硬编码
- 上下文及时清理:ThreadLocal 必须在 finally/afterCompletion 清除
- 响应规范统一:拦截失败返回统一 JSON 结构与状态码
- 日志可观测:记录关键拦截决策点(但避免打印敏感信息)
- 性能可评估:重逻辑不要堆在拦截层,必要时做缓存与短路
- 安全优先复用成熟框架:复杂认证授权尽量用 Spring Security
- 测试覆盖:对拦截路径、排除路径、异常路径写集成测试
- 灰度与开关:关键拦截策略支持配置开关,便于紧急回滚
十二、一个推荐的企业项目落地方案
给一个实用分层模板:
- Filter 1(TraceFilter):生成/透传 TraceId,写 MDC
- Filter 2(RequestWrapperFilter):包装 request/response 便于重复读取与审计
- Filter 3(CORSFilter):统一跨域头处理(或改为配置方式)
- Interceptor 1(AuthInterceptor):登录态识别、用户上下文注入
- Interceptor 2(PermissionInterceptor):接口权限校验
- Interceptor 3(AuditInterceptor):记录关键业务操作审计信息
- GlobalExceptionHandler:统一异常响应
- AOP/Service层:承接更细粒度业务日志与性能统计
这套组合的核心思想是:
入口层做"通用基础能力",MVC 层做"业务拦截能力",业务层做"领域逻辑能力"。
不要把所有逻辑都塞进 Filter 或 Interceptor。
Filter 与 Interceptor 是 Java Web 开发中最基础、也最容易被低估的两种机制。它们都能"拦截请求",但拦截层级、语义边界和最佳使用场景完全不同。真正高质量的工程实践,不是争论"谁更强",而是根据架构分层把它们用在最合适的位置:
- Filter:靠前、通用、底层、协议与容器相关能力
- Interceptor:贴近业务、贴近 Controller、适合鉴权与业务横切逻辑
当你能清晰地划分这两者的职责,并把顺序、异常、路径、上下文管理做到规范化时,拦截机制就不再只是"登录校验插件",而会成为你系统治理能力的重要基础设施。
如果把全文浓缩成一句话,那就是:
在 Java Web 中,Filter 决定"请求能否规范进入系统",Interceptor 决定"请求能否按业务规则被处理"。两者协同,才是可维护、可扩展、可观测的拦截体系。