@WebFilter不生效的根本原因是Spring Boot默认不扫描它,必须添加@ServletComponentScan;且无法用@Order控制顺序,加载顺序由类名决定。

@WebFilter 注解为什么经常不生效
根本原因就一个:@WebFilter 是 Servlet 原生规范,Spring Boot 默认不扫描它——必须显式加 @ServletComponentScan 才能被识别。但即使加了,你也无法用 @Order 控制执行顺序,多个 @WebFilter 的加载顺序由类名字母序决定,完全不可控。
常见错误现象包括:
- 加了
@WebFilter(urlPatterns = "/*")却没任何日志输出,怀疑过滤器没运行 - 同时存在
AuthFilter和LogFilter,结果LogFilter总在AuthFilter之前执行,权限校验逻辑被绕过 - 在测试环境生效,上线后因类加载器差异或 jar 包打包方式不同而失效
适用场景极少:仅限单过滤器、无顺序依赖、且你明确接受 Servlet 容器级管理(比如集成老式 Filter 库)。
FilterRegistrationBean 是唯一可靠的方式
FilterRegistrationBean 把 Filter 当作 Spring Bean 管理,天然支持依赖注入、生命周期回调和精确排序。它的核心优势不是“更高级”,而是“可预测”——setOrder(1) 就是真的排第一,setOrder(2) 就是第二,不会被类名或扫描顺序干扰。
实操建议:
- Filter 实现类用
@Component或直接 new 出来都行,但推荐不加@Component,避免被 Spring 自动注册两次 - URL 模式用
addUrlPatterns("/*"),不要用setUrlPatterns(Arrays.asList("/*")),后者会覆盖默认的空列表,导致拦截失效 - 务必调用
setFilter(new YourFilter()),漏掉这句等于注册了个空壳 - 如果 Filter 需要注入其他 Bean(比如
RedisTemplate),必须通过构造函数或@Autowired在 Filter 类内部完成,不能指望FilterRegistrationBean替你做依赖注入
示例关键片段:
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<TimeFilter> timeFilterRegistration() {
FilterRegistrationBean<TimeFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new TimeFilter()); // 必须设 filter 实例
registration.addUrlPatterns("/*");
registration.setName("timeFilter");
registration.setOrder(1); // 这里才真正生效
return registration;
}
}
为什么不能混用@WebFilter和FilterRegistrationBean
混用会导致同一个 Filter 被注册两次:一次由 Servlet 容器通过 @WebFilter 加载,一次由 Spring 通过 FilterRegistrationBean 注册。后果是请求被重复处理,比如耗时统计翻倍、Token 校验执行两遍、甚至响应体被写两次引发 IllegalStateException: getWriter() has already been called 错误。
排查方法很简单:启动日志里搜 Filter initializing 或你的 Filter 名字,如果出现两次,基本就是混用了。
安全做法只选一种,且项目初期就定死。团队协作中建议统一禁用 @WebFilter,在代码检查(如 SonarQube)里加规则拦截 @WebFilter 注解的出现。
Filter 和拦截器执行时机的真实差异
很多人以为 “Filter → Interceptor → Controller” 是线性流程,其实 Filter 的 doFilter 方法里包裹了整个请求生命周期——包括 Interceptor 的 preHandle / afterCompletion,甚至 DispatcherServlet 的 doDispatch。所以 Filter 的 chain.doFilter() 一执行,后面所有 Spring Web 层逻辑(含拦截器)才开始跑。
这意味着:
- 你在 Filter 中修改
HttpServletRequest(比如包装 request),Interceptor 拿到的就是已包装的对象;但反过来,Interceptor 改不了 Filter 已经读过的请求体(比如已 parse 的 body) - Filter 的
destroy()不会在应用关闭时立即调用,可能延迟几秒甚至更久;而 Interceptor 的afterCompletion()是每次请求必达的最后钩子 - Filter 可以处理静态资源(/css/*.js)、错误页(/error)、甚至 Actuator 端点;Interceptor 默认不拦截这些,除非显式配置
excludePathPatterns
真正需要跨框架兼容(比如未来换 Netty 或 WebFlux)的逻辑,别放 Filter 里——那是 Servlet 绑定的,换容器就废。
Filter 的排序、执行范围和容器绑定性,是它最常出问题的三个锚点。别信“加个注解就行”,每个setOrder、每条 addUrlPatterns、每次 chain.doFilter() 的位置,都在悄悄决定请求能不能走到 Controller。










