1. OncePerRequestFilter 基础认知在Spring框架的过滤器体系中OncePerRequestFilter是个特殊的存在。这个抽象类位于org.springframework.web.filter包下专门设计用来确保每个HTTP请求只被过滤一次。与常规Filter接口的实现不同它通过请求属性标记的机制有效避免了在Servlet容器中由于过滤器链配置不当导致的重复执行问题。关键区别常规Filter可能在forward/include时重复触发而OncePerRequestFilter通过request.setAttribute()机制保证单次执行实际开发中遇到过这样的场景在统计接口耗时的时候发现同一个请求的过滤器逻辑被执行了多次。排查后发现是过滤器链中存在转发(forward)操作导致普通Filter被重复触发。改用OncePerRequestFilter后问题迎刃而解这正是它最典型的应用场景。2. 核心工作机制解析2.1 执行流程剖析OncePerRequestFilter的执行流程可以分解为以下关键步骤前置检查阶段检查请求属性中是否存在ALREADY_FILTERED标记验证shouldNotFilter()方法返回值子类可覆盖确认当前请求是否支持异步处理核心过滤阶段对于需要处理的请求设置ALREADY_FILTERED属性调用doFilterInternal()抽象方法子类必须实现在finally块中清除请求属性标记异常处理机制统一处理Filter执行过程中的异常确保在异常情况下也能正确清理请求状态// 典型执行流程代码示意 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { String alreadyFilteredAttribute getAlreadyFilteredAttributeName(); if (request.getAttribute(alreadyFilteredAttribute) ! null) { filterChain.doFilter(request, response); return; } request.setAttribute(alreadyFilteredAttribute, Boolean.TRUE); try { // 实际过滤逻辑实现 doFilterInternal(request, response, filterChain); } finally { request.removeAttribute(alreadyFilteredAttribute); } }2.2 线程安全考量在多线程环境下OncePerRequestFilter通过以下机制保证线程安全请求属性(attribute)的作用域限定在单个请求生命周期内使用synchronized块保护关键属性的读写操作异步请求处理时自动禁用过滤逻辑可通过setEnableAsyncSupport配置3. 实战应用场景3.1 请求日志记录构建可复用的请求日志过滤器时需要特别注意日志信息应包括请求URI和HTTP方法客户端IP地址请求参数敏感信息需脱敏响应状态码请求处理耗时性能优化点使用MDC(Mapped Diagnostic Context)实现请求追踪对静态资源请求跳过详细日志记录采用异步方式写入日志文件public class RequestLoggingFilter extends OncePerRequestFilter { private static final Logger logger LoggerFactory.getLogger(RequestLoggingFilter.class); Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { long startTime System.currentTimeMillis(); try { chain.doFilter(request, response); } finally { long duration System.currentTimeMillis() - startTime; logger.info({} {} - {}ms [{}], request.getMethod(), request.getRequestURI(), duration, response.getStatus()); } } Override protected boolean shouldNotFilter(HttpServletRequest request) { return request.getRequestURI().startsWith(/static/); } }3.2 认证授权控制在实现JWT认证过滤器时OncePerRequestFilter能有效避免认证逻辑的重复执行典型处理流程从请求头提取Authorization令牌验证JWT签名和有效期解析用户身份并设置SecurityContext对无效令牌返回401响应安全注意事项必须验证令牌签名算法检查令牌有效期时考虑时钟偏移敏感错误信息不应直接返回客户端关键安全实践认证失败时应立即终止请求处理不继续执行过滤器链4. 高级配置技巧4.1 动态过滤规则通过覆盖shouldNotFilter方法实现灵活的条件过滤Override protected boolean shouldNotFilter(HttpServletRequest request) { // 排除Swagger文档接口 if (request.getRequestURI().contains(/v3/api-docs)) { return true; } // 排除健康检查端点 if (/actuator/health.equals(request.getRequestURI())) { return true; } return super.shouldNotFilter(request); }4.2 异步请求处理针对异步Servlet请求的特殊处理配置启用异步支持setEnableAsyncSupport(true);异步上下文监听if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return; }异步结果处理AsyncContext asyncContext request.startAsync(); asyncContext.addListener(new MyAsyncListener());5. 性能调优指南5.1 过滤顺序优化在Spring Security环境中过滤器的执行顺序至关重要推荐顺序从先到后请求日志记录跨域处理(CORS)认证过滤器授权过滤器资源访问过滤器通过Order注解或FilterRegistrationBean显式指定Bean public FilterRegistrationBeanRequestLoggingFilter loggingFilter() { FilterRegistrationBeanRequestLoggingFilter registration new FilterRegistrationBean(); registration.setFilter(new RequestLoggingFilter()); registration.setOrder(Ordered.HIGHEST_PRECEDENCE 1); return registration; }5.2 资源过滤豁免高效实现静态资源过滤跳过路径匹配策略private static final String[] STATIC_RESOURCES { /css/**, /js/**, /images/**, /webjars/**, /favicon.ico }; Override protected boolean shouldNotFilter(HttpServletRequest request) { return Arrays.stream(STATIC_RESOURCES) .anyMatch(pattern - new AntPathMatcher().match(pattern, request.getRequestURI())); }性能对比Ant风格路径匹配耗时约0.02ms/次正则表达式匹配耗时约0.15ms/次简单字符串匹配最快适用于固定路径6. 常见问题排查6.1 过滤器不生效可能原因及解决方案现象可能原因解决方案过滤器未执行未正确注册为Spring Bean使用Component或Bean显式声明重复执行错误实现了普通Filter接口改为继承OncePerRequestFilter部分请求未过滤shouldNotFilter逻辑有误调试检查条件判断逻辑6.2 与Spring Security集成问题典型集成冲突场景执行顺序冲突自定义过滤器需要在SecurityFilterChain之前执行解决方案设置Order(Ordered.HIGHEST_PRECEDENCE 1)上下文污染SecurityContext被意外清除确保在doFilter之后清理线程局部变量CSRF保护干扰对某些API需要禁用CSRF配置.csrf().ignoringRequestMatchers(...)7. 扩展应用模式7.1 请求参数预处理实现统一参数校验和转换日期格式标准化String dateParam request.getParameter(createTime); if (dateParam ! null) { LocalDateTime parsedDate LocalDateTime.parse(dateParam, DateTimeFormatter.ISO_DATE_TIME); request.setAttribute(normalizedCreateTime, parsedDate); }SQL注入防护MapString, String[] sanitizedParams new HashMap(); request.getParameterMap().forEach((key, values) - { String[] sanitized Arrays.stream(values) .map(this::sanitizeSql) .toArray(String[]::new); sanitizedParams.put(key, sanitized); });7.2 响应后处理实现统一响应包装Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { ContentCachingResponseWrapper responseWrapper new ContentCachingResponseWrapper(response); try { chain.doFilter(request, responseWrapper); if (response.getContentType() ! null response.getContentType().contains(application/json)) { String responseBody new String(responseWrapper.getContentAsByteArray(), response.getCharacterEncoding()); // 统一包装响应结构 ApiResponse? wrappedResponse ApiResponse.success(parseResponseBody(responseBody)); String jsonResponse objectMapper.writeValueAsString(wrappedResponse); responseWrapper.resetBuffer(); responseWrapper.getWriter().write(jsonResponse); } } finally { responseWrapper.copyBodyToResponse(); } }8. 最佳实践总结继承选择简单场景直接继承OncePerRequestFilter复杂场景结合GenericFilterBean获取Spring配置能力性能关键点静态资源应尽早跳过过滤避免在过滤器中执行耗时IO操作考虑启用异步处理提升吞吐量安全红线永远不要修改请求输入流敏感操作必须放在过滤器链末端异常处理不能泄露系统信息测试建议模拟forward/include请求验证单次执行压力测试验证线程安全性覆盖各种HTTP方法测试在微服务架构下OncePerRequestFilter还可以与Gateway层面的过滤器配合使用形成多层次的请求处理管道。实际项目中我们通常会组合使用多个特性过滤器比如先执行日志记录再进行权限校验最后处理响应包装每个过滤器各司其职通过明确的执行顺序构建可靠的请求处理流水线。