1. 项目概述
在Spring框架的Web开发中,过滤器(Filter)是处理HTTP请求的重要组件。OncePerRequestFilter作为Spring提供的过滤器基类,解决了传统Servlet Filter在特殊场景下的重复执行问题。本文将深入剖析其实现原理、典型应用场景和实战技巧。
2. 核心需求解析
2.1 为什么需要OncePerRequestFilter
在传统Servlet Filter实现中,当请求经过Forward或Include分发时,过滤器链会被重复执行。这会导致:
- 性能损耗:重复执行相同过滤逻辑
- 业务异常:如重复提交校验、重复日志记录
- 资源竞争:如重复获取数据库连接
Spring的OncePerRequestFilter通过请求属性标记机制,确保每个请求只被过滤一次,无论经过多少次请求分发。
2.2 典型应用场景
- 认证鉴权:JWT校验、OAuth2令牌验证
- 请求日志:记录请求/响应基本信息
- 参数处理:统一参数解码/校验
- 性能监控:接口耗时统计
- 跨域处理:CORS头信息设置
3. 实现原理深度解析
3.1 核心源码分析
public abstract class OncePerRequestFilter extends GenericFilterBean { public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); if (request.getAttribute(alreadyFilteredAttributeName) != null) { filterChain.doFilter(request, response); } else { request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { doFilterNested(request, response, filterChain); } finally { request.removeAttribute(alreadyFilteredAttributeName); } } } }关键设计点:
- 使用
ALREADY_FILTERED_SUFFIX作为属性后缀 - 通过
getAlreadyFilteredAttributeName()生成唯一属性名 - 采用try-finally保证属性清理
3.2 与传统Filter对比
| 特性 | 传统Servlet Filter | OncePerRequestFilter |
|---|---|---|
| 重复执行风险 | 存在 | 不存在 |
| 请求属性管理 | 手动处理 | 自动管理 |
| Spring集成度 | 低 | 高 |
| 异常处理 | 基础 | 增强 |
4. 实战应用指南
4.1 基础实现示例
public class JwtAuthFilter extends OncePerRequestFilter { @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String token = request.getHeader("Authorization"); if (!validateToken(token)) { response.sendError(HttpStatus.UNAUTHORIZED.value()); return; } chain.doFilter(request, response); } private boolean validateToken(String token) { // JWT验证逻辑 } }4.2 高级配置技巧
- 过滤路径配置:
@Bean public FilterRegistrationBean<JwtAuthFilter> jwtFilter() { FilterRegistrationBean<JwtAuthFilter> reg = new FilterRegistrationBean<>(); reg.setFilter(new JwtAuthFilter()); reg.addUrlPatterns("/api/*"); reg.setOrder(Ordered.HIGHEST_PRECEDENCE); return reg; }- 异步请求支持:
@Override protected boolean shouldNotFilterAsyncDispatch() { return false; // 默认true表示不处理ASYNC分发 }- 错误处理增强:
@Override protected void doFilterNested( HttpServletRequest request, HttpServletResponse response, FilterChain chain) { try { super.doFilterNested(request, response, chain); } catch (BusinessException e) { handleBusinessException(e, response); } }5. 性能优化与最佳实践
5.1 性能关键点
- 属性操作优化:
- 使用
request.getAttribute()而非getParameter() - 属性名生成避免复杂计算
- 过滤逻辑优化:
@Override protected boolean shouldNotFilter(HttpServletRequest request) { return request.getRequestURI().startsWith("/public/"); }- 线程安全保证:
- 避免在Filter中使用实例变量
- 必须使用的共享资源需同步处理
5.2 常见问题解决方案
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 过滤器未生效 | 顺序配置错误 | 调整@Order或FilterRegistrationBean |
| 重复执行 | 未正确继承 | 检查是否extends OncePerRequestFilter |
| 异步请求丢失上下文 | 默认不处理ASYNC | 重写shouldNotFilterAsyncDispatch |
| 性能下降 | 复杂属性计算 | 简化alreadyFiltered属性名生成逻辑 |
6. 高级应用场景
6.1 微服务链路追踪
public class TraceIdFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(...) { String traceId = request.getHeader("X-Trace-ID"); if (StringUtils.isEmpty(traceId)) { traceId = UUID.randomUUID().toString(); } MDC.put("traceId", traceId); try { chain.doFilter(request, response); } finally { MDC.remove("traceId"); } } }6.2 请求限流控制
public class RateLimitFilter extends OncePerRequestFilter { private final RateLimiter limiter = RateLimiter.create(100); // 100QPS @Override protected void doFilterInternal(...) { if (!limiter.tryAcquire()) { response.sendError(429, "Too Many Requests"); return; } chain.doFilter(request, response); } }6.3 响应内容修改
public class ResponseWrapperFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(...) { ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); chain.doFilter(request, wrappedResponse); byte[] content = wrappedResponse.getContentAsByteArray(); String modified = new String(content).replace("old", "new"); wrappedResponse.resetBuffer(); wrappedResponse.getWriter().write(modified); wrappedResponse.copyBodyToResponse(); } }7. 测试与调试技巧
7.1 单元测试方案
@SpringBootTest class JwtFilterTest { @Autowired private FilterChainProxy filterChain; @Test void testFilter() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("Authorization", "valid_token"); MockHttpServletResponse response = new MockHttpServletResponse(); filterChain.doFilter(request, response); assertEquals(200, response.getStatus()); } }7.2 调试关键点
- 断点位置:
- doFilterInternal入口
- shouldNotFilter条件判断
- 异常处理分支
- 观察变量:
- request.getAttribute(alreadyFilteredAttributeName)
- 请求URI和参数
- 响应状态码
- 日志增强:
@Override protected void initFilterBean() { logger.info("Filter initialized with order: {}", getOrder()); }8. 生产环境经验
在实际项目中应用OncePerRequestFilter时,有几个关键经验值得分享:
- 顺序管理:通过@Order或FilterRegistrationBean明确指定过滤顺序,特别是安全相关过滤器应设置最高优先级。我曾遇到因顺序不当导致的安全绕过问题,后来通过以下配置解决:
@Bean public FilterRegistrationBean<SecurityFilter> securityFilter() { FilterRegistrationBean<SecurityFilter> reg = new FilterRegistrationBean<>(); reg.setFilter(new SecurityFilter()); reg.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); return reg; }- 上下文清理:在finally块中确保清理ThreadLocal等上下文资源,避免内存泄漏。建议采用模板方法模式:
@Override protected final void doFilterInternal(...) { try { // 前置处理 doActualFiltering(request, response, chain); } finally { // 必须执行的清理逻辑 cleanupContext(); } } protected abstract void doActualFiltering(...);- 性能监控:为关键过滤器添加耗时统计,推荐使用StopWatch:
StopWatch watch = new StopWatch(); watch.start(); try { chain.doFilter(request, response); } finally { watch.stop(); metrics.recordTime(watch.getTotalTimeMillis()); }- 动态配置:结合配置中心实现动态开关:
@Value("${filter.enabled:true}") private boolean enabled; @Override protected boolean shouldNotFilter(HttpServletRequest request) { return !enabled || super.shouldNotFilter(request); }