# 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提供超时值。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/webservlet/1.spring-web-mvc/1.5asynchronous-requests.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
