Skip to content

异步请求 (Asynchronous Requests)

Spring MVC 与 Servlet 3.0+ 的异步请求处理有着深入的集成。异步处理允许控制器释放 Servlet 线程,转而在后台线程处理耗时操作,从而提高应用系统的吞吐量。

核心组件

1. DeferredResult

DeferredResult 允许你在不同的线程中设置处理结果。你只需在控制器中返回它,Spring 会保持连接打开,直到你调用了 setResult

java
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<>();
    // 将 deferredResult 交给外部事件监听器或后台任务处理
    return deferredResult;
}

// 在其他线程或任务完成后:
deferredResult.setResult(result);
kotlin
@GetMapping("/quotes")
@ResponseBody
fun quotes(): DeferredResult<String> {
    val deferredResult = DeferredResult<String>()
    // ...
    return deferredResult
}

2. Callable

控制器可以返回 Callable。Spring MVC 会自动调用 AsyncTaskExecutor 来异步执行它,当任务完成后,再将结果返回给 Servlet 容器进行后续处理。

java
@PostMapping("/upload")
public Callable<String> processUpload(MultipartFile file) {
    return () -> {
        // 耗时的文件处理逻辑
        return "viewName";
    };
}

3. WebAsyncTask

类似于 Callable,但提供了更多的配置选项,如超时处理(Timeout)和指定特定的执行器(Executor)。


HTTP 流式处理 (Streaming)

如果你需要产生多个异步值并将它们逐步写入响应,可以使用流式处理。

1. ResponseBodyEmitter

用于发送一系列对象,每个对象都会通过 HttpMessageConverter 序列化。

2. SseEmitter

ResponseBodyEmitter 的子类,专门用于 Server-Sent Events (SSE),数据格式符合 W3C SSE 规范。

3. StreamingResponseBody

用于直接绕过消息转换,直接向响应的 OutputStream 写入原始数据(非常适合大文件下载)。


响应式类型 (Reactive Types)

Spring MVC 甚至支持在控制器中返回响应式类型,如 MonoFlux (Reactor),或者 Observable (RxJava)。

  • 单值类型(如 Mono):效果类似于 DeferredResult
  • 多值流(如 Flux):如果 Media Type 是 text/event-streamapplication/x-ndjson,效果类似于 SSE 流。

配置要求

  1. Servlet 容器:必须在 web.xml 中将 <async-supported>true</async-supported> 添加到 DispatcherServlet 和所有 Filter,或者在 Java 配置中自动启用。
  2. 线程池:默认使用的线程池不适合在高负载生产环境下使用,务必配置自定义的 AsyncTaskExecutor

补充教学

1. 异步请求的“往返”流程

当你返回 DeferredResultCallable 时:

  1. Servlet 线程进入控制器,启动异步模式,然后立即返回线程池(线程变为空闲)。
  2. 此时请求并未结束,连接由 Servlet 容器保持,但没有任何线程在阻塞等待。
  3. 业务逻辑在后台线程计算完毕,调用 setResult
  4. Spring MVC 会发起一次内部分发 (Internal Dispatch)
  5. Servlet 线程再次被分配来处理结果,就像控制器刚返回了这个值一样进行视图渲染。

2. Spring MVC 异步 vs WebFlux

  • Spring MVC:基于 Servlet 3.0+ 规范。它解决了“线程在等待 IO 时被阻塞”的问题,但在写入响应(Write)时仍然是阻塞的(尽管是在专门的线程池里写)。
  • WebFlux:从头到尾的非阻塞。不仅路由和逻辑是非阻塞的,连底层网络 IO 写入也是非阻塞的。它不需要为每个并发连接分配这么重的线程维护,资源利用率更高。

3. 超时处理的最佳实践

异步请求最大的风险是客户端断开了连接而服务端却还在跑。

  • 始终为 DeferredResult 设置超时时间:new DeferredResult<>(timeoutValue, errorResult)
  • 捕获 AsyncRequestTimeoutException 并通过 @ExceptionHandler 返回友好的提示,避免连接池假死。

Based on Spring Framework.