Spring Boot 多个 Filter 指定执行顺序实践
项目中经常同时存在多个 Filter(过滤器),比如登录校验、请求日志、跨域处理、XSS 过滤等。如果不显式指定顺序,它们的执行先后是不确定的,容易出现「日志里拿不到登录用户信息」「CORS 响应头被覆盖」「次数统计记在了鉴权之前」这类难以排查的问题。
本文整理 Spring Boot 中指定多个 Filter 执行顺序的三种方式,并给出对比、顺序规则与常见失效原因。
一、执行顺序的核心规则
Spring Boot 中 Filter 的执行顺序由 order 值决定:
数字越小,优先级越高,越先执行;
请求进入时,按 order 升序依次执行各 Filter 中
chain.doFilter()之前的代码;响应返回时,按相反顺序执行
chain.doFilter()之后的代码,整体是「栈式」结构;未指定 order 的 Filter,默认取
Ordered.LOWEST_PRECEDENCE(即Integer.MAX_VALUE),排在最后。
二、方式一:@Order 注解
直接在实现了 Filter 接口的类上使用 @Order 注解,最为简洁。
代码示例
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
@Component
@Order(1) // 优先级高
public class FirstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化代码
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 过滤逻辑
System.out.println("FirstFilter is executed");
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 清理资源
}
}
@Component
@Order(2) // 优先级低
public class SecondFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化代码
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 过滤逻辑
System.out.println("SecondFilter is executed");
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 清理资源
}
}说明
适用于「自己编写 + 交给 Spring 管理」的 Filter;
底层由
ServletContextInitializerBeans收集所有Filter类型的 Bean,再通过AnnotationAwareOrderComparator排序,所以@Order才能被识别;前提是 Filter 类必须被 Spring 扫描到(带
@Component)。若是第三方库通过web.xml或 Servlet 容器注册的 Filter,@Order不会生效。
三、方式二:实现 Ordered 接口
@Order 的本质就是 Ordered 接口的注解版。当顺序值需要由代码动态计算(例如根据配置、环境决定)时,直接实现接口更灵活。
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
@Component
public class SecondFilter implements Filter, Ordered {
@Override
public int getOrder() {
return 2; // 数字越小,优先级越高
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("SecondFilter is executed");
chain.doFilter(request, response);
}
}优先级说明:当同一个类上同时存在 @Order 和 getOrder() 时,Spring 的排序比较器会优先读取注解值,因此两者不要同时使用,避免混淆。
四、方式三:FilterRegistrationBean(推荐)
如果 Filter 类没有被 Spring 管理(例如来自第三方库、需要 new 出来),或者需要按 URL 路径精确控制生效范围,推荐显式注册。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<FirstFilter> firstFilterRegistration() {
FilterRegistrationBean<FirstFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new FirstFilter());
registration.addUrlPatterns("/*");
registration.setName("firstFilter");
registration.setOrder(1); // 数字越小,优先级越高
return registration;
}
@Bean
public FilterRegistrationBean<SecondFilter> secondFilterRegistration() {
FilterRegistrationBean<SecondFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new SecondFilter());
registration.addUrlPatterns("/api/*");
registration.setName("secondFilter");
registration.setOrder(2);
return registration;
}
}常用配置项
注意:用这种方式注册后,Filter 类本身不要再加 @Component,否则会被 Spring Boot 自动注册一次、又被手动注册一次,导致同一个 Filter 执行两遍。
五、三种方式对比
六、常见问题排查
1. 为什么 @Order 没生效?
Filter 类缺少
@Component,压根没被 Spring 扫描到;Filter 是通过
@Bean方法返回的 —— 此时@Order标注在类上无效,需要把@Order加到@Bean方法上,或改用FilterRegistrationBean;Filter 由第三方 Starter 自动配置注册,顺序被框架固定。
2. 与 Spring Security 的先后关系
Spring Security 的过滤器链(springSecurityFilterChain)默认 order 是 -100(常量 SecurityProperties.DEFAULT_FILTER_ORDER)。因此自定义 Filter 若想先于 Security 执行,order 需要小于 -100;想后于 Security 执行,order 需要大于 -100。
3. javax 还是 jakarta?
Spring Boot 2.x 使用的是 javax.servlet;从 Spring Boot 3.x 起已迁移到 jakarta.servlet。升级版本时记得同步替换 import 包名,否则会出现类找不到或 Filter 完全不执行的问题。
4. order 值相同会怎样?
order 值相同时执行顺序不确定,取决于 Bean 的注册顺序,且不同机器/不同版本可能不一致。建议为每个 Filter 分配不同值,并按 10、20、30 的步长留出插入空间。
5. Filter 与 Interceptor 的顺序
Filter 属于 Servlet 规范,在 DispatcherServlet 之前执行;Interceptor 属于 Spring MVC,在 DispatcherServlet 之后执行。所以无论 order 怎么设置,Filter 一定先于所有 Interceptor。
七、验证执行顺序
为两个 Filter 的 doFilter 前后各加一行日志,访问任意接口后观察输出:
FirstFilter is executed
SecondFilter is executed
SecondFilter is done
FirstFilter is done可以看到:请求方向与响应方向刚好相反,这就是前面提到的「栈式」结构。理解这一点,对处理「响应头包装」「请求体重复读取」「耗时统计」等场景非常关键 —— 统计耗时的代码必须放在 chain.doFilter() 之后才能拿到完整时间。
八、小结
顺序规则只有一条:order 数字越小越先执行,响应阶段反向回流。
简单的自研 Filter 用
@Order即可,别忘了@Component。需要按 URL 路径控制、或 Filter 不受 Spring 管理时,用
FilterRegistrationBean显式注册,并避免重复注册。与 Spring Security 配合时,记住基准值是
-100。给 order 留出步长,避免撞车导致顺序不可控。
评论区