Spring OncePerRequestFilter原理与实战应用
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 FilterOncePerRequestFilter重复执行风险存在不存在请求属性管理手动处理自动管理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 FilterRegistrationBeanJwtAuthFilter jwtFilter() { FilterRegistrationBeanJwtAuthFilter 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 FilterRegistrationBeanSecurityFilter securityFilter() { FilterRegistrationBeanSecurityFilter 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(...);性能监控为关键过滤器添加耗时统计推荐使用StopWatchStopWatch 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); }