> For the complete documentation index, see [llms.txt](https://docs.flydean.com/spring-framework-documentation5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flydean.com/spring-framework-documentation5/webservlet/1.spring-web-mvc/1.5asynchronous-requests.md).

# 1.5 异步请求

Spring MVC与Servlet 3.0异步请求处理具有广泛的集成：

* 控制器方法中的DeferredResult和Callable返回值，并为单个异步返回值提供基本支持。
* 控制器可以流式传输多个值，包括SSE和原始数据。
* 控制器可以使用反应式客户端并返回反应式类型以进行响应处理。

## 1.5.1 DeferredResult

一旦在Servlet容器中启用了异步请求处理功能，控制器方法就可以使用DeferredResult包装任何受支持的控制器方法返回值，如以下示例所示：

```java
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// From some other thread...
deferredResult.setResult(data);
```

控制器可以从另一个线程异步生成返回值，例如，响应外部事件（JMS消息），计划任务或其他事件。

## 1.5.2 Callable

控制器可以使用java.util.concurrent.Callable包装任何受支持的返回值，如以下示例所示：

```java
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}
```

然后，可以通过配置的TaskExecutor运行给定任务来获取返回值。

## 1.5.3 Processing

这是Servlet异步请求处理的非常简洁的概述：

* 可以通过调用request.startAsync（）将ServletRequest置于异步模式。这样做的主要作用是可以退出Servlet（以及所有过滤器），但是响应保持打开状态，以便以后完成处理。
* 对request.startAsync（）的调用返回AsyncContext，可将其用于进一步控制异步处理。例如，它提供了分派方法，该方法与Servlet API中的转发方法类似，不同之处在于，它使应用程序可以恢复对Servlet容器线程的请求处理。
* ServletRequest提供对当前DispatcherType的访问，您可以使用它来区分处理初始请求，异步分派，转发和其他分派器类型。

DeferredResult处理工作如下：

* 控制器返回DeferredResult并将其保存在一些内存队列或列表中，可以在其中访问。
* Spring MVC调用request.startAsync（）。
* 同时，DispatcherServlet和所有已配置的过滤器退出请求处理线程，但响应保持打开状态。
* 应用程序从某个线程设置DeferredResult，Spring MVC将请求分派回Servlet容器。
* 再次调用DispatcherServlet，并使用异步产生的返回值恢复处理。

Callable的处理方式如下：

* 控制器返回一个Callable。
* Spring MVC调用request.startAsync（）并将Callable提交给TaskExecutor以在单独的线程中进行处理。
* 同时，DispatcherServlet和所有过滤器退出Servlet容器线程，但响应保持打开状态。
* 最终，Callable产生一个结果，Spring MVC将请求分派回Servlet容器以完成处理。
* 再次调用DispatcherServlet，并使用Callable中异步产生的返回值恢复处理。

有关更多背景知识，您还可以阅读在Spring MVC 3.2中引入了异步请求处理支持的博客文章。

**Exception Handling**

使用DeferredResult时，可以选择是调用带有异常的setResult还是setErrorResult。在这两种情况下，Spring MVC都将请求分派回Servlet容器以完成处理。然后将其视为控制器方法返回了给定值，或者好像它产生了给定的异常。然后，异常将通过常规的异常处理机制进行处理（例如，调用@ExceptionHandler方法）。

使用Callable时，会发生类似的处理逻辑，主要区别是从Callable返回结果或引发异常。

**拦截**

HandlerInterceptor实例的类型可以为AsyncHandlerInterceptor，以在启动异步处理的初始请求（而不是postHandle和afterCompletion）上接收afterConcurrentHandlingStarted回调。

HandlerInterceptor实现也可以注册CallableProcessingInterceptor或DeferredResultProcessingInterceptor，以与异步请求的生命周期进行更深入的集成（例如，处理超时事件）。有关更多详细信息，请参见AsyncHandlerInterceptor。

DeferredResult提供onTimeout（Runnable）和onCompletion（Runnable）回调。有关更多详细信息，请参见DeferredResult的javadoc。可以用Callable代替WebAsyncTask，它公开了超时和完成回调的其他方法。

**与WebFlux相比**

Servlet API最初是为通过Filter-Servlet链进行一次传递而构建的。 Servlet 3.0中添加了异步请求处理，使应用程序可以退出Filter-Servlet链，但保留响应以进行进一步处理。 Spring MVC异步支持围绕该机制构建。当控制器返回DeferredResult时，退出Filter-Servlet链，并释放Servlet容器线程。稍后，在设置DeferredResult时，将进行ASYNC调度（到相同的URL），在此期间，控制器将再次映射，但不是调用它，而是使用DeferredResult值（就像控制器返回了它）来恢复处理。 。

相比之下，Spring WebFlux既不是基于Servlet API构建的，也不需要这种异步请求处理功能，因为它在设计上是异步的。异步处理已内置在所有框架协定中，并在请求处理的所有阶段得到内在支持。

从编程模型的角度来看，Spring MVC和Spring WebFlux都支持异步和响应类型作为控制器方法中的返回值。 Spring MVC甚至支持流式传输，包括反应性背压。但是，与WebFlux不同，WebFlux依赖于非阻塞I / O，并且每次写入都不需要额外的线程，因此对响应的单个写入仍然处于阻塞状态（并在单独的线程上执行）。

另一个基本区别是，Spring MVC在控制器方法参数中不支持异步或响应类型（例如，@ RequestBody，@ RequestPart等），也没有对异步和响应类型作为模型属性的任何显式支持。 Spring WebFlux确实支持所有这些。

## 1.5.4 HTTP Streaming

您可以将DeferredResult和Callable用于单个异步返回值。 如果要产生多个异步值并将那些值写入响应，该怎么办？ 本节介绍如何执行此操作。

**对象**

您可以使用ResponseBodyEmitter返回值生成对象流，其中每个对象都使用HttpMessageConverter序列化并写入响应，如以下示例所示：

```java
@GetMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();
```

您还可以将ResponseBodyEmitter用作ResponseEntity的主体，以自定义响应的状态和标头。

当发射器抛出IOException时（例如，如果远程客户端离开了），应用程序将不负责清理连接，并且不应调用Emitter.complete或Emitter.completeWithError。 取而代之的是，该Servlet容器自动启动AsyncListener错误通知，其中Spring MVC在其中进行completeWithError调用。 依次，此调用对应用程序执行一个最终的ASYNC调度，在此期间，Spring MVC调用已配置的异常解析器并完成请求。

**SSE**

SseEmitter（ResponseBodyEmitter的子类）提供对服务器发送事件的支持，其中从服务器发送的事件根据W3C SSE规范进行格式化。 要从控制器生成SSE流，请返回SseEmitter，如以下示例所示：

```java
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
    SseEmitter emitter = new SseEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();
```

虽然SSE是流式传输到浏览器的主要选项，但请注意Internet Explorer不支持服务器发送事件。 考虑将Spring的WebSocket消息与针对广泛浏览器的SockJS后备传输（包括SSE）结合使用。

另请参阅上一节以获取有关异常处理的注解。

**Raw Data**

有时，绕过消息转换并直接流到响应OutputStream很有用（例如，用于文件下载）。 您可以使用StreamingResponseBody返回值类型来执行此操作，如以下示例所示：

```java
@GetMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}
```

您可以将StreamingResponseBody用作ResponseEntity中的主体，以自定义响应的状态和标头。

## 1.5.5 Reactive Types

Spring MVC支持在控制器中使用反应式客户端库（另请参阅WebFlux部分中的反应式库）。 这包括来自spring-webflux的WebClient和其他资源，例如Spring Data反应数据存储库。 在这种情况下，能够从控制器方法返回反应类型是很方便的。

反应性返回值的处理方式如下：

* 与使用DeferredResult相似，单值承诺也适用于此。 示例包括Mono（Reactor）或Single（RxJava）。
* 与使用ResponseBodyEmitter或SseEmitter相似，适用于具有流媒体类型（例如application / stream + json或text / event-stream）的多值流。 示例包括Flux（反应堆）或Observable（RxJava）。 应用程序还可以返回Flux \<ServerSentEvent>或Observable \<ServerSentEvent>。
* 类似于使用DeferredResult \<List <？>>，适用于任何其他媒体类型（例如application / json）的多值流。

Spring MVC通过spring-core的ReactiveAdapterRegistry支持Reactor和RxJava，这使其可以适应多个反应式库。

为了流式传输到响应，支持反应性背压，但响应的写仍处于阻塞状态，并通过配置的TaskExecutor在单独的线程上执行，以避免阻塞上游源（例如，从WebClient返回的Flux）。 默认情况下，SimpleAsyncTaskExecutor用于阻止写操作，但是在负载下不适合。 如果计划使用响应类型进行流传输，则应使用MVC配置来配置任务执行程序。

## 1.5.6 Disconnects

当远程客户端离开时，Servlet API不提供任何通知。 因此，在通过SseEmitter或响应类型流式传输到响应时，定期发送数据很重要，因为如果客户端断开连接，写入将失败。 发送可以采取空（仅评论）SSE事件或另一端必须将其解释为心跳和忽略的任何其他数据的形式。

或者，考虑使用具有内置心跳机制的Web消息传递解决方案（例如，基于WebSocket的STOMP或具有SockJS的WebSocket）。

## 1.5.7 Configuration

必须在Servlet容器级别启用异步请求处理功能。 MVC配置还为异步请求提供了多个选项。

**Servlet容器**

过滤器和Servlet声明具有asyncSupported标志，需要将其设置为true才能启用异步请求处理。另外，应声明过滤器映射以处理ASYNC javax.servlet.DispatchType。

在Java配置中，当您使用AbstractAnnotationConfigDispatcherServletInitializer初始化Servlet容器时，这是自动完成的。

在web.xml配置中，可以将\<async-supported> true \</ async-supported>添加到DispatcherServlet和Filter声明中，并添加\<dispatcher> ASYNC \</ dispatcher>来过滤映射。

**Spring MVC**

MVC配置公开了以下与异步请求处理相关的选项：

* Java配置：在WebMvcConfigurer上使用configureAsyncSupport回调。
* XML名称空间：使用\<mvc：annotation-driven>下的\<async-support>元素。

您可以配置以下内容：

* 异步请求的默认超时值（如果未设置）取决于底层的Servlet容器（例如，在Tomcat上为10秒）。
* AsyncTaskExecutor用于在使用响应类型进行流式传输时阻止写入，并用于执行从控制器方法返回的Callable实例。如果您使用反应类型进行流式传输或具有返回Callable的控制器方法，我们强烈建议配置此属性，因为默认情况下，它是SimpleAsyncTaskExecutor。
* DeferredResultProcessingInterceptor实现和CallableProcessingInterceptor实现。

请注意，您还可以在DeferredResult，ResponseBodyEmitter和SseEmitter上设置默认超时值。对于Callable，可以使用WebAsyncTask提供超时值。
