Spring MVC 中使用 DeferredResult 实现异步请求需两步:一是确保 Servlet 容器支持异步(3.0+,web.xml 需显式配置 async-supported,Spring Boot 默认启用),二是 Controller 方法返回并管理 DeferredResult 实例,配合线程池触发 setResult、注册 onTimeout/onCompletion 回调,并注意并发安全与资源清理。

Spring MVC 中使用 DeferredResult 实现异步请求,核心不是“配置”一堆 XML 或注解开关,而是两步到位:确保容器支持异步 + Controller 正确返回并管理 DeferredResult 实例。关键点在于 Servlet 容器层面的启用和 Spring 的适配,而非 Spring 自身额外开启某项功能。
确保 Servlet 容器支持异步(必须)
DeferredResult 依赖 Servlet 3.0+ 的异步 API(request.startAsync()),所以第一步是确认并启用容器异步能力:
- 若用
web.xml(传统部署):在和标签内显式添加true - 若用 Java Config(如
ServletWebServerFactory):Spring Boot 默认已启用(Tomcat/Jetty/Undertow 均默认支持),无需额外配置;但需保证依赖的javax.servlet-api版本 ≥ 3.1.0 - Spring Boot 2.0+ 项目中,只要不手动禁用,
DispatcherServlet和所有 Filter 自动继承容器异步支持,无需额外声明
Controller 方法返回 DeferredResult(核心写法)
方法签名直接返回 DeferredResult,不阻塞主线程,由 Spring 自动触发 startAsync() 并挂起响应:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 可指定超时时间(毫秒)和超时默认值:
new DeferredResult(5000L, "timeout") - 结果必须由其他线程调用
setResult()(成功)或setErrorResult()(异常),主线程才会继续写响应 - 推荐配合线程池、消息监听、定时任务等外部触发源,避免在 Controller 内部用
new Thread()硬编码
设置回调逻辑(可选但实用)
利用 onCompletion() 和 onTimeout() 注册清理动作,防止内存泄漏或状态残留:
-
deferredResult.onCompletion(() -> cache.remove(key)):无论成功或超时都执行 -
deferredResult.onTimeout(() -> log.warn("Request {} timed out", key)):仅超时触发 - 务必在 setResult 前完成注册,否则回调可能不生效
注意线程安全与资源管理
DeferredResult 本身线程安全,但持有它的集合(如 Map、Queue)需用并发容器:
- 存储多个 DeferredResult 时,用
ConcurrentHashMap或ConcurrentLinkedQueue,避免 HashMap 在多线程 put 时死循环 - 避免将 DeferredResult 存入 Session 或静态变量长期持有;建议搭配唯一 ID(如 UUID 或用户标识)做键值缓存,并设 TTL 或主动清理
- 超时后未调用 setResult 的实例,其回调仍会执行,但不能再调用 setResult —— 否则抛 IllegalStateException










