You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
        }
    }
}

关键改动说明

  1. 用AsyncContext托管请求生命周期:调用servletRequest.startAsync()后,Tomcat会保留请求对象直到asyncContext.complete()被调用,避免对象被提前回收。
  2. 封装过滤链与异步上下文:通过QueuedRequest类绑定请求对应的FilterChain,确保延迟执行时能正确走完整的过滤流程。
  3. 异步执行队列请求:使用asyncContext.start()在独立线程中处理队列请求,不会阻塞定时任务线程。
  4. 超时与流量控制:给异步上下文设置超时时间,同时限制定时任务每秒处理的请求数,避免队列积压后瞬间释放导致系统过载。

额外注意事项

  • 确保项目依赖的Servlet版本在3.0以上(主流Spring Boot版本均满足)。
  • 如果使用Jetty等其他Servlet容器,异步上下文的核心逻辑一致,仅细节配置可能略有差异。
  • 生产环境建议使用成熟的限流组件(如Resilience4j、Guava RateLimiter),减少自定义实现的维护成本。

内容的提问来源于stack exchange,提问作者WounderWaffle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:05:56