Spring自定义Filter请求排队报错:请求对象已回收的修复方案
问题原因分析
你遇到的IllegalStateException本质是Tomcat容器的请求对象生命周期限制:当原始请求处理线程结束后,Tomcat会回收或复用HttpServletRequest/HttpServletResponse对应的底层对象(比如RequestFacade)。你把请求封装成Runnable放入队列延迟执行时,原线程已经完成,这些对象已经脱离了Tomcat的请求上下文,再调用filterChain.doFilter自然会触发"对象已被回收"的异常。
修复方案
核心思路是使用Servlet 3.0+的**异步上下文(AsyncContext)**来持有请求响应的有效引用,异步上下文会让Tomcat保持请求对象的生命周期直到异步处理完成。同时需要将请求对应的过滤链一起封装,避免过滤链失效。
修改后的完整Filter代码
@Component @Slf4j @EnableScheduling public class ApiRequestFilter implements Filter { private final AtomicInteger requestCounter = new AtomicInteger(0); private final int MAX_NUMB_OF_REQUESTS_PER_SECOND = 2; private final BlockingQueue<QueuedRequest> requestQueue = new LinkedBlockingQueue<>(); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if (servletRequest instanceof HttpServletRequest httpServletRequest) { int currentCount = requestCounter.getAndIncrement(); String uri = httpServletRequest.getRequestURI(); log.warn("Incoming request: {}, request counter: {}", uri, currentCount); if (currentCount >= MAX_NUMB_OF_REQUESTS_PER_SECOND) { // 开启异步上下文,保持请求对象生命周期 AsyncContext asyncContext = servletRequest.startAsync(); // 设置超时时间,避免请求无限等待 asyncContext.setTimeout(10000); try { // 封装异步上下文和对应过滤链,存入队列 requestQueue.put(new QueuedRequest(asyncContext, filterChain)); } catch (InterruptedException e) { log.error("Failed to queue request", e); ((HttpServletResponse) servletResponse).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Too many requests, queue full"); asyncContext.complete(); } } else { // 直接处理请求 filterChain.doFilter(servletRequest, servletResponse); } } else { ((HttpServletResponse) servletResponse).sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad Request"); } } @Scheduled(timeUnit = TimeUnit.SECONDS, fixedDelay = 1, initialDelay = 0) private void processQueuedRequests() { // 重置请求计数器 requestCounter.set(0); // 控制每秒处理的请求数,避免流量突增 int processed = 0; while (!requestQueue.isEmpty() && processed < MAX_NUMB_OF_REQUESTS_PER_SECOND) { try { QueuedRequest queuedRequest = requestQueue.take(); AsyncContext asyncContext = queuedRequest.getAsyncContext(); FilterChain filterChain = queuedRequest.getFilterChain(); // 在异步线程中执行请求处理,不阻塞定时任务线程 asyncContext.start(() -> { try { filterChain.doFilter(asyncContext.getRequest(), asyncContext.getResponse()); } catch (IOException | ServletException e) { log.error("Error processing queued request", e); ((HttpServletResponse) asyncContext.getResponse()).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } finally { // 完成异步上下文,释放Tomcat请求资源 asyncContext.complete(); } }); processed++; } catch (InterruptedException e) { log.error("Error processing queued request", e); } } } // 内部类:封装异步上下文和对应过滤链 private static class QueuedRequest { private final AsyncContext asyncContext; private final FilterChain filterChain; public QueuedRequest(AsyncContext asyncContext, FilterChain filterChain) { this.asyncContext = asyncContext; this.filterChain = filterChain; } public AsyncContext getAsyncContext() { return asyncContext; } public FilterChain getFilterChain() { return filterChain; } } }
关键改动说明
- 用AsyncContext托管请求生命周期:调用
servletRequest.startAsync()后,Tomcat会保留请求对象直到asyncContext.complete()被调用,避免对象被提前回收。 - 封装过滤链与异步上下文:通过
QueuedRequest类绑定请求对应的FilterChain,确保延迟执行时能正确走完整的过滤流程。 - 异步执行队列请求:使用
asyncContext.start()在独立线程中处理队列请求,不会阻塞定时任务线程。 - 超时与流量控制:给异步上下文设置超时时间,同时限制定时任务每秒处理的请求数,避免队列积压后瞬间释放导致系统过载。
额外注意事项
- 确保项目依赖的Servlet版本在3.0以上(主流Spring Boot版本均满足)。
- 如果使用Jetty等其他Servlet容器,异步上下文的核心逻辑一致,仅细节配置可能略有差异。
- 生产环境建议使用成熟的限流组件(如Resilience4j、Guava RateLimiter),减少自定义实现的维护成本。
内容的提问来源于stack exchange,提问作者WounderWaffle
相关产品推荐
相关产品推荐

