1.6. 异步请求

预计阅读时间: 22 分钟

Spring MVC has an extensive integration with Servlet 3.0 asynchronous request processing:
Spring MVC 与 Servlet 3.0 异步请求处理有广泛集成:

  • DeferredResult and Callable return values in controller methods provide basic support for a single asynchronous return value.
    DeferredResultCallable 在控制器方法中返回值提供对单个异步返回值的基本支持。

  • Controllers can stream multiple values, including SSE and raw data.
    控制器可以流式传输多个值,包括 SSE 和原始数据。

  • Controllers can use reactive clients and return reactive types for response handling.
    控制器可以使用反应式客户端并返回反应式类型以处理响应。

(#mvc-ann-async-deferredresult)1.6.1.DeferredResult

Once the asynchronous request processing feature is enabled in the Servlet container, controller methods can wrap any supported controller method return value with DeferredResult, as the following example shows:
一旦在 Servlet 容器中启用异步请求处理功能,控制器方法可以将任何受支持的控制器方法返回值包装为 DeferredResult ,如下例所示:

@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(result);
@GetMapping("/quotes") @ResponseBody fun quotes(): DeferredResult<String> { val deferredResult = DeferredResult<String>() // Save the deferredResult somewhere.. return deferredResult } // From some other thread... deferredResult.setResult(result)

The controller can produce the return value asynchronously, from a different thread — for example, in response to an external event (JMS message), a scheduled task, or other event.
控制器可以异步地、从不同的线程生成返回值——例如,响应外部事件(JMS 消息)、计划任务或其他事件。

(#mvc-ann-async-callable)1.6.2.Callable

A controller can wrap any supported return value with java.util.concurrent.Callable, as the following example shows:
控制器可以将任何支持的返回值包裹在 java.util.concurrent.Callable 中,如下例所示:

@PostMapping public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() { public String call() throws Exception { // ... return "someView"; } }; }
@PostMapping fun processUpload(file: MultipartFile) = Callable<String> { // ... "someView" }

The return value can then be obtained by running the given task through the configured TaskExecutor.
返回值可以通过运行配置的 TaskExecutor 来获取。

(#mvc-ann-async-processing)1.6.3. Processing 1.6.3. 处理

Here is a very concise overview of Servlet asynchronous request processing:
这里是对 Servlet 异步请求处理的一个非常简洁的概述:

  • A ServletRequest can be put in asynchronous mode by calling request.startAsync(). The main effect of doing so is that the Servlet (as well as any filters) can exit, but the response remains open to let processing complete later.
    一个 ServletRequest 可以通过调用 request.startAsync() 放入异步模式。这样做的主要效果是 Servlet(以及任何过滤器)可以退出,但响应仍然保持开放,以便稍后完成处理。

  • The call to request.startAsync() returns AsyncContext, which you can use for further control over asynchronous processing. For example, it provides the dispatch method, which is similar to a forward from the Servlet API, except that it lets an application resume request processing on a Servlet container thread.
    调用 request.startAsync() 返回 AsyncContext ,您可以用它来进一步控制异步处理。例如,它提供了 dispatch 方法,这与 Servlet API 中的转发类似,但允许应用程序在 Servlet 容器线程上恢复请求处理。

  • The ServletRequest provides access to the current DispatcherType, which you can use to distinguish between processing the initial request, an asynchronous dispatch, a forward, and other dispatcher types.
    ServletRequest 提供了对当前 DispatcherType 的访问,您可以使用它来区分处理初始请求、异步调度、转发和其他调度类型。

DeferredResult processing works as follows:
DeferredResult 处理工作如下:

  • The controller returns a DeferredResult and saves it in some in-memory queue or list where it can be accessed.
    控制器返回一个 DeferredResult 并将其保存在某个内存队列或列表中,以便访问。

  • Spring MVC calls request.startAsync().
    Spring MVC 调用 request.startAsync()

  • Meanwhile, the DispatcherServlet and all configured filters exit the request processing thread, but the response remains open.
    同时, DispatcherServlet 以及所有配置的过滤器退出请求处理线程,但响应保持打开状态。

  • The application sets the DeferredResult from some thread, and Spring MVC dispatches the request back to the Servlet container.
    应用程序从某个线程设置 DeferredResult ,然后 Spring MVC 将请求派发回 Servlet 容器。

  • The DispatcherServlet is invoked again, and processing resumes with the asynchronously produced return value.
    DispatcherServlet 再次被调用,处理继续进行,使用异步产生的返回值。

Callable processing works as follows:
Callable 处理工作如下:

  • The controller returns a Callable.
    控制器返回一个 Callable

  • Spring MVC calls request.startAsync() and submits the Callable to a TaskExecutor for processing in a separate thread.
    Spring MVC 调用 request.startAsync() 并将 Callable 提交给 TaskExecutor ,在单独的线程中进行处理。

  • Meanwhile, the DispatcherServlet and all filters exit the Servlet container thread, but the response remains open.
    同时, DispatcherServlet 以及所有过滤器都退出了 Servlet 容器线程,但响应仍然打开。

  • Eventually the Callable produces a result, and Spring MVC dispatches the request back to the Servlet container to complete processing.
    最终, Callable 生成结果,Spring MVC 将请求回派给 Servlet 容器以完成处理。

  • The DispatcherServlet is invoked again, and processing resumes with the asynchronously produced return value from the Callable.
    DispatcherServlet 再次被调用,处理从 Callable 异步产生的返回值继续进行。

For further background and context, you can also read the blog posts that introduced asynchronous request processing support in Spring MVC 3.2.
为了进一步了解背景和上下文,您还可以阅读介绍 Spring MVC 3.2 中异步请求处理支持的博客文章。

(#mvc-ann-async-exceptions)Exception Handling 异常处理

When you use a DeferredResult, you can choose whether to call setResult or setErrorResult with an exception. In both cases, Spring MVC dispatches the request back to the Servlet container to complete processing. It is then treated either as if the controller method returned the given value or as if it produced the given exception.
当您使用一个 DeferredResult 时,可以选择是否调用 setResultsetErrorResult ,但有例外。在两种情况下,Spring MVC 都会将请求回派给 Servlet 容器以完成处理。然后,它要么被视为控制器方法返回了给定的值,要么被视为产生了给定的异常。
The exception then goes through the regular exception handling mechanism (for example, invoking @ExceptionHandler methods).
异常随后通过常规异常处理机制(例如,调用 @ExceptionHandler 方法)进行处理。

When you use Callable, similar processing logic occurs, the main difference being that the result is returned from the Callable or an exception is raised by it.
当你使用 Callable 时,会触发类似的处理逻辑,主要区别在于结果是返回自 Callable 或者它抛出一个异常。

(#mvc-ann-async-interception)Interception 拦截

HandlerInterceptor instances can be of type AsyncHandlerInterceptor, to receive the afterConcurrentHandlingStarted callback on the initial request that starts asynchronous processing (instead of postHandle and afterCompletion).
HandlerInterceptor 实例可以是 AsyncHandlerInterceptor 类型,以接收启动异步处理的初始请求的 afterConcurrentHandlingStarted 回调(而不是 postHandleafterCompletion )。

HandlerInterceptor implementations can also register a CallableProcessingInterceptor or a DeferredResultProcessingInterceptor, to integrate more deeply with the lifecycle of an asynchronous request (for example, to handle a timeout event). See AsyncHandlerInterceptor for more details.
HandlerInterceptor 实现也可以注册一个 CallableProcessingInterceptor 或一个 DeferredResultProcessingInterceptor ,以更深入地与异步请求的生命周期集成(例如,处理超时事件)。有关更多详细信息,请参阅 AsyncHandlerInterceptor

DeferredResult provides onTimeout(Runnable) and onCompletion(Runnable) callbacks. See the javadoc of DeferredResult for more details. Callable can be substituted for WebAsyncTask that exposes additional methods for timeout and completion callbacks.
DeferredResult 提供了 onTimeout(Runnable)onCompletion(Runnable) 回调。有关详细信息,请参阅 DeferredResult 的 javadoc。 Callable 可以替换为 WebAsyncTask ,后者提供了额外的超时和完成回调方法。

(#mvc-ann-async-vs-webflux)Compared to WebFlux 与 WebFlux 相比

The Servlet API was originally built for making a single pass through the Filter-Servlet chain. Asynchronous request processing, added in Servlet 3.0, lets applications exit the Filter-Servlet chain but leave the response open for further processing.
Servlet API 最初是为了在 Filter-Servlet 链中实现单次遍历而构建的。Servlet 3.0 中添加的异步请求处理允许应用程序退出 Filter-Servlet 链,但将响应保持开放以供进一步处理。
The Spring MVC asynchronous support is built around that mechanism. When a controller returns a DeferredResult, the Filter-Servlet chain is exited, and the Servlet container thread is released. Later, when the DeferredResult is set, an ASYNC dispatch (to the same URL) is made, during which the controller is mapped again but, rather than invoking it, the DeferredResult value is used (as if the controller returned it) to resume processing.
Spring MVC 的异步支持建立在那种机制之上。当控制器返回一个 DeferredResult 时,Filter-Servlet 链退出,Servlet 容器线程被释放。稍后,当 DeferredResult 设置后,进行一个 ASYNC 调度(到相同的 URL),在此期间控制器再次被映射,但不是调用它,而是使用 DeferredResult 值(就像控制器返回它一样)来继续处理。

By contrast, Spring WebFlux is neither built on the Servlet API, nor does it need such an asynchronous request processing feature, because it is asynchronous by design.
相比之下,Spring WebFlux 既不是基于 Servlet API 构建的,也不需要这样的异步请求处理功能,因为它本身就是异步的。
Asynchronous handling is built into all framework contracts and is intrinsically supported through all stages of request processing.
异步处理内置在所有框架合约中,并在请求处理的各个阶段内在支持。

From a programming model perspective, both Spring MVC and Spring WebFlux support asynchronous and Reactive Types as return values in controller methods. Spring MVC even supports streaming, including reactive back pressure.
从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持在控制器方法中返回异步和响应式类型。Spring MVC 甚至支持流式传输,包括反应式背压。
However, individual writes to the response remain blocking (and are performed on a separate thread), unlike WebFlux, which relies on non-blocking I/O and does not need an extra thread for each write.
然而,对响应的单独写入仍然是阻塞的(并在单独的线程上执行),与依赖于非阻塞 I/O 且不需要为每个写入额外线程的 WebFlux 不同。

Another fundamental difference is that Spring MVC does not support asynchronous or reactive types in controller method arguments (for example, @RequestBody, @RequestPart, and others), nor does it have any explicit support for asynchronous and reactive types as model attributes. Spring WebFlux does support all that.
另一个基本区别是,Spring MVC 不支持在控制器方法参数中使用异步或响应式类型(例如, @RequestBody@RequestPart 等),也没有对异步和响应式类型作为模型属性进行任何明确的支持。Spring WebFlux 则支持所有这些。

(#mvc-ann-async-http-streaming)1.6.4. HTTP Streaming 1.6.4. HTTP 流媒体

You can use DeferredResult and Callable for a single asynchronous return value. What if you want to produce multiple asynchronous values and have those written to the response? This section describes how to do so.
您可以使用 DeferredResultCallable 来返回单个异步值。那么,如果您想产生多个异步值并将它们写入响应中呢?本节将描述如何实现这一点。

(#mvc-ann-async-objects)Objects 对象

You can use the ResponseBodyEmitter return value to produce a stream of objects, where each object is serialized with an HttpMessageConverter and written to the response, as the following example shows:
您可以使用 ResponseBodyEmitter 返回值来生成一个对象流,其中每个对象都使用 HttpMessageConverter 序列化并写入响应,如下例所示:

@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();
@GetMapping("/events") fun handle() = ResponseBodyEmitter().apply { // Save the emitter somewhere.. } // In some other thread emitter.send("Hello once") // and again later on emitter.send("Hello again") // and done at some point emitter.complete()

You can also use ResponseBodyEmitter as the body in a ResponseEntity, letting you customize the status and headers of the response.
您还可以使用 ResponseBodyEmitter 作为 ResponseEntity 的主体,让您可以自定义响应的状态和头信息。

When an emitter throws an IOException (for example, if the remote client went away), applications are not responsible for cleaning up the connection and should not invoke emitter.complete or emitter.completeWithError. Instead, the servlet container automatically initiates an AsyncListener error notification, in which Spring MVC makes a completeWithError call. This call, in turn, performs one final ASYNC dispatch to the application, during which Spring MVC invokes the configured exception resolvers and completes the request.
当抛出 emitter (例如,如果远程客户端断开连接)时,应用程序不负责清理连接,不应调用 emitter.completeemitter.completeWithError 。相反,Servlet 容器自动启动一个 AsyncListener 错误通知,其中 Spring MVC 执行一个 completeWithError 调用。这个调用反过来执行对应用程序的最后一次 ASYNC 调度,在此期间 Spring MVC 调用配置的异常解析器并完成请求。

(#mvc-ann-async-sse)SSE

SseEmitter (a subclass of ResponseBodyEmitter) provides support for Server-Sent Events, where events sent from the server are formatted according to the W3C SSE specification. To produce an SSE stream from a controller, return SseEmitter, as the following example shows:
SseEmitterResponseBodyEmitter 的子类)提供了对服务器端事件的支撑,服务器发送的事件格式遵循 W3C SSE 规范。要从控制器生成 SSE 流,返回 SseEmitter ,如下例所示:

@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();
@GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) fun handle() = SseEmitter().apply { // Save the emitter somewhere.. } // In some other thread emitter.send("Hello once") // and again later on emitter.send("Hello again") // and done at some point emitter.complete()

While SSE is the main option for streaming into browsers, note that Internet Explorer does not support Server-Sent Events. Consider using Spring’s WebSocket messaging with SockJS fallback transports (including SSE) that target a wide range of browsers.
虽然 SSE 是向浏览器进行流式传输的主要选项,请注意,Internet Explorer 不支持服务器端事件。考虑使用 Spring 的 WebSocket 消息传递,并使用 SockJS 回退传输(包括 SSE),以针对广泛的浏览器。

See also previous section for notes on exception handling.
参见上一节关于异常处理的说明。

(#mvc-ann-async-output-stream)Raw Data 原始数据

Sometimes, it is useful to bypass message conversion and stream directly to the response OutputStream (for example, for a file download). You can use the StreamingResponseBody return value type to do so, as the following example shows:
有时,绕过消息转换并直接将流传输到响应 OutputStream (例如,用于文件下载)是有用的。您可以使用以下示例中的 StreamingResponseBody 返回值类型来实现这一点:

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

You can use StreamingResponseBody as the body in a ResponseEntity to customize the status and headers of the response.
您可以使用 StreamingResponseBody 作为 ResponseEntity 的正文来自定义响应的状态和头信息。

(#mvc-ann-async-reactive-types)1.6.5. Reactive Types 1.6.5. 反应式类型

Spring MVC supports use of reactive client libraries in a controller (also read Reactive Libraries in the WebFlux section). This includes the WebClient from spring-webflux and others, such as Spring Data reactive data repositories. In such scenarios, it is convenient to be able to return reactive types from the controller method.
Spring MVC 支持在控制器中使用反应式客户端库(也可参阅 WebFlux 部分的反应式库)。这包括来自 spring-webfluxWebClient 以及其他,例如 Spring Data 反应式数据仓库。在这种情况下,从控制器方法返回反应式类型是方便的。

Reactive return values are handled as follows:
响应式返回值处理方式如下:

  • A single-value promise is adapted to, similar to using DeferredResult. Examples include Mono (Reactor) or Single (RxJava).
    单一值承诺类似于使用 DeferredResult 。例如包括 Mono (Reactor)或 Single (RxJava)。

  • A multi-value stream with a streaming media type (such as application/x-ndjson or text/event-stream) is adapted to, similar to using ResponseBodyEmitter or SseEmitter. Examples include Flux (Reactor) or Observable ( RxJava). Applications can also return Flux<ServerSentEvent> or Observable<ServerSentEvent>.
    一个具有流媒体类型的多个值流(如 application/x-ndjsontext/event-stream )被适配,类似于使用 ResponseBodyEmitterSseEmitter 。示例包括 Flux (Reactor)或 Observable (RxJava)。应用程序还可以返回 Flux<ServerSentEvent>Observable<ServerSentEvent>

  • A multi-value stream with any other media type (such as application/json) is adapted to, similar to using DeferredResult<List<?>>.
    一个多值流被适配为任何其他媒体类型(如 application/json ),类似于使用 DeferredResult<List<?>>

Spring MVC supports Reactor and RxJava through the ReactiveAdapterRegistry from spring-core, which lets it adapt from multiple reactive libraries.
Spring MVC 通过 ReactiveAdapterRegistryspring-core 支持 Reactor 和 RxJava,这使得它可以适应多个响应式库。

For streaming to the response, reactive back pressure is supported, but writes to the response are still blocking and are run on a separate thread through the configured TaskExecutor, to avoid blocking the upstream source (such as a Flux returned from WebClient). By default, SimpleAsyncTaskExecutor is used for the blocking writes, but that is not suitable under load. If you plan to stream with a reactive type, you should use the MVC configuration to configure a task executor.
对于将内容流式传输到响应,支持反应式背压,但写入响应仍然是阻塞的,并且通过配置的 TaskExecutor 在单独的线程上运行,以避免阻塞上游源(例如,从 Flux 返回的 WebClient )。默认情况下,使用 SimpleAsyncTaskExecutor 进行阻塞写入,但在负载下这不合适。如果您计划使用反应式类型进行流式传输,应使用 MVC 配置来配置任务执行器。

(#mvc-ann-async-disconnects)1.6.6. Disconnects 1.6.6. 断开连接

The Servlet API does not provide any notification when a remote client goes away. Therefore, while streaming to the response, whether through SseEmitter or reactive types, it is important to send data periodically, since the write fails if the client has disconnected. The send could take the form of an empty (comment-only) SSE event or any other data that the other side would have to interpret as a heartbeat and ignore.
Servlet API 不提供任何远程客户端断开连接时的通知。因此,在通过 SseEmitter 或响应式类型向响应流式传输数据时,定期发送数据非常重要,因为如果客户端已断开连接,写入将失败。发送可以采取空(仅注释)SSE 事件或任何其他对方必须将其解释为心跳并忽略的数据的形式。

Alternatively, consider using web messaging solutions (such as STOMP over WebSocket or WebSocket with SockJS) that have a built-in heartbeat mechanism.
或者考虑使用具有内置心跳机制的 Web 消息解决方案(如 STOMP over WebSocket 或 WebSocket with SockJS)。

(#mvc-ann-async-configuration)1.6.7. Configuration 1.6.7. 配置

The asynchronous request processing feature must be enabled at the Servlet container level. The MVC configuration also exposes several options for asynchronous requests.
异步请求处理功能必须在 Servlet 容器级别启用。MVC 配置还公开了几个异步请求的选项。

(#mvc-ann-async-configuration-servlet3)Servlet Container Servlet 容器

Filter and Servlet declarations have an asyncSupported flag that needs to be set to true to enable asynchronous request processing. In addition, Filter mappings should be declared to handle the ASYNC javax.servlet.DispatchType.
过滤器(Filter)和 Servlet 声明有一个 asyncSupported 标志,需要设置为 true 以启用异步请求处理。此外,应声明过滤器映射来处理 ASYNC javax.servlet.DispatchType

In Java configuration, when you use AbstractAnnotationConfigDispatcherServletInitializer to initialize the Servlet container, this is done automatically.
在 Java 配置中,当您使用 AbstractAnnotationConfigDispatcherServletInitializer 初始化 Servlet 容器时,这是自动完成的。

In web.xml configuration, you can add <async-supported>true</async-supported> to the DispatcherServlet and to Filter declarations and add <dispatcher>ASYNC</dispatcher> to filter mappings.
web.xml 配置中,您可以将 <async-supported>true</async-supported> 添加到 DispatcherServletFilter 声明中,并将 <dispatcher>ASYNC</dispatcher> 添加到过滤映射中。

(#mvc-ann-async-configuration-spring-mvc)Spring MVC

The MVC configuration exposes the following options related to asynchronous request processing:
MVC 配置公开了以下与异步请求处理相关的选项:

  • Java configuration: Use the configureAsyncSupport callback on WebMvcConfigurer.
    Java 配置:在 WebMvcConfigurer 上使用 configureAsyncSupport 回调。

  • XML namespace: Use the <async-support> element under <mvc:annotation-driven>.
    XML 命名空间:使用 <mvc:annotation-driven> 下的 <async-support> 元素。

You can configure the following:
您可以配置以下内容:

  • Default timeout value for async requests, which if not set, depends on the underlying Servlet container.
    异步请求的默认超时值,如果未设置,则取决于底层的 Servlet 容器。

  • AsyncTaskExecutor to use for blocking writes when streaming with Reactive Types and for executing Callable instances returned from controller methods. We highly recommended configuring this property if you stream with reactive types or have controller methods that return Callable, since by default, it is a SimpleAsyncTaskExecutor.
    AsyncTaskExecutor 用于在流式传输时使用 Reactive Types 进行阻塞写入以及执行从控制器方法返回的 Callable 实例。我们强烈建议配置此属性,如果您使用 Reactive Types 进行流式传输或控制器方法返回 Callable ,因为默认情况下,它是一个 SimpleAsyncTaskExecutor

  • DeferredResultProcessingInterceptor implementations and CallableProcessingInterceptor implementations.
    DeferredResultProcessingInterceptor 实现 和 CallableProcessingInterceptor 实现。

Note that you can also set the default timeout value on a DeferredResult, a ResponseBodyEmitter, and an SseEmitter. For a Callable, you can use WebAsyncTask to provide a timeout value.
请注意,您还可以在 DeferredResultResponseBodyEmitterSseEmitter 上设置默认超时值。对于 Callable ,您可以使用 WebAsyncTask 来提供超时值。