spring OncePerRequestFilter 作用
概要
OncePerRequestFilter
是 Spring Web 提供的一个抽象滤器基类,用于保证在一次 HTTP 请求的整个分派过程中,该滤器仅执行一次,无论该请求经历了多少次内部转发(forward)、包含(include)或错误/异步分派。它通过在请求属性中打标记来判断自身是否已执行过,并提供了一个只需关注业务逻辑的 doFilterInternal
方法,同时支持子类根据需要决定是否在错误或异步 dispatch 中跳过过滤。
核心作用与工作原理
-
保证单次执行
-
默认情况下,
OncePerRequestFilter
会在过滤链开始时检查请求属性(属性名由getAlreadyFilteredAttributeName()
确定),若标记已存在则跳过,未标记则执行并打标记,确保同一次请求不会重复过滤 (OncePerRequestFilter (Spring Framework 6.2.6 API), OncePerRequestFilter - Spring)。
-
-
简化子类实现
-
子类只需重写
doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
,聚焦业务逻辑,不必关心重复执行的问题 (OncePerRequestFilter (Spring Framework 6.2.6 API))。
-
-
异步与错误分派控制
-
默认
OncePerRequestFilter
在普通、ERROR、ASYNC 三种 dispatch 类型中均执行过滤;子类可通过重写shouldNotFilterAsyncDispatch()
或shouldNotFilterErrorDispatch()
来排除异步或错误分派情形下的执行 (OncePerRequestFilter - Spring, Add the ability for OncePerRequestFilter to determine if it should ...)。
-
关键方法
-
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
实现了对“已过滤”标记的检查与设置,并最终调用doFilterInternal(...)
或直接跳过。 -
protected abstract void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
子类在此方法中编写实际的过滤逻辑(如认证、日志、头处理等),执行完成后必须调用filterChain.doFilter(request, response)
以继续后续处理。 -
protected boolean shouldNotFilterAsyncDispatch()
返回true
时会跳过在异步 dispatch 场景下的过滤,默认false
。 -
protected boolean shouldNotFilterErrorDispatch()
返回true
时会跳过在错误 dispatch 情形下的过滤,默认false
。
常见使用场景
-
Spring Security 定制过滤器
Authentication、CSRF、Header 处理等安全相关过滤器通常继承自OncePerRequestFilter
,确保安全检查仅执行一次,而不会因内部转发而重复认证 (Architecture :: Spring Security)。 -
请求日志与限流
在高并发场景下,通过自定义OncePerRequestFilter
打印请求日志、记录链路或进行请求计数,避免重复记录导致数据冗余。 -
统一异常处理或上下文初始化
为每次请求初始化线程上下文、数据库连接或绑定 MDC(Mapped Diagnostic Context),并在请求结束前清理资源。
注意事项
-
务必调用
filterChain.doFilter
若漏写此调用,将导致后续过滤器及 Servlet 无法执行。 -
异步请求处理
对于启用了 Servlet 3.0+ 异步支持的应用,如不希望在异步回调线程再次执行过滤逻辑,可重写shouldNotFilterAsyncDispatch()
返回true
。 -
标记名称冲突
默认过滤标记名称与具体滤器实例名称相关,如有定制需求可重写getAlreadyFilteredAttributeName()
。 -
注入与依赖
若在doFilterInternal
中需注入 Spring Bean,请确保过滤器已通过 Spring 容器管理(如注册为@Component
或在FilterRegistrationBean
中声明),否则可能拿不到依赖(详见 StackOverflow 讨论) (Dependency Injection into Spring Servlet context ... - Stack Overflow)。