Asynchronous Requests
-
DeferredResult 或 Callable,用于单个异步返回值。
-
ResponseBodyEmitter、SseEmitter 或 StreamingResponseBody,用于流式传输多个返回值。
-
响应式类型,例如 Mono、Flux 或 Single,用于支持与响应式客户端库集成。
Spring MVC 的异步请求处理与 Spring WebFlux 不同,它依赖于 Servlet API 的异步功能,而 Spring WebFlux 是异步设计的。Spring MVC 不支持控制器方法参数中的异步类型,而 Spring WebFlux 则支持。
Spring MVC 与 Servlet 的异步请求 processing 进行广泛集成:
Spring MVC has an extensive integration with Servlet asynchronous request processing:
-
DeferredResult
andCallable
return values in controller methods provide basic support for a single asynchronous return value. -
Controllers can stream multiple values, including SSE and raw data.
-
Controllers can use reactive clients and return reactive types for response handling.
关于这与 Spring WebFlux 有何不同,请参阅下面的 Async Spring MVC compared to WebFlux 部分。
For an overview of how this differs from Spring WebFlux, see the Async Spring MVC compared to WebFlux section below.
DeferredResult
一旦在 Servlet 容器中 enabled了异步请求处理功能,控制器方法就可以使用 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:
-
Java
-
Kotlin
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<>();
// 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)
控制器可以从不同的线程异步生成返回值 - 例如,响应外部事件(JMS 消息)、预定任务或其他事件。
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.
Callable
控制器可以用 java.util.concurrent.Callable
包装任何受支持的返回值,如下示例所示:
A controller can wrap any supported return value with java.util.concurrent.Callable
,
as the following example shows:
-
Java
-
Kotlin
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return () -> "someView";
}
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
// ...
"someView"
}
然后可以通过运行 configured AsyncTaskExecutor
中给定的任务来获得返回值。
The return value can then be obtained by running the given task through the
configured AsyncTaskExecutor
.
Processing
以下是对 Servlet 异步请求处理的非常简洁的概述:
Here is a very concise overview of Servlet asynchronous request processing:
-
A
ServletRequest
can be put in asynchronous mode by callingrequest.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. -
The call to
request.startAsync()
returnsAsyncContext
, which you can use for further control over asynchronous processing. For example, it provides thedispatch
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. -
The
ServletRequest
provides access to the currentDispatcherType
, which you can use to distinguish between processing the initial request, an asynchronous dispatch, a forward, and other dispatcher types.
DeferredResult
处理按如下方式进行:
DeferredResult
processing works as follows:
-
The controller returns a
DeferredResult
and saves it in some in-memory queue or list where it can be accessed. -
Spring MVC calls
request.startAsync()
. -
Meanwhile, the
DispatcherServlet
and all configured filters exit the request processing thread, but the response remains open. -
The application sets the
DeferredResult
from some thread, and Spring MVC dispatches the request back to the Servlet container. -
The
DispatcherServlet
is invoked again, and processing resumes with the asynchronously produced return value.
Callable
处理按如下方式进行:
Callable
processing works as follows:
-
The controller returns a
Callable
. -
Spring MVC calls
request.startAsync()
and submits theCallable
to anAsyncTaskExecutor
for processing in a separate thread. -
Meanwhile, the
DispatcherServlet
and all filters exit the Servlet container thread, but the response remains open. -
Eventually the
Callable
produces a result, and Spring MVC dispatches the request back to the Servlet container to complete processing. -
The
DispatcherServlet
is invoked again, and processing resumes with the asynchronously produced return value from theCallable
.
为了进一步了解背景和上下文,你还可以阅读介绍 Spring MVC 3.2 中异步请求处理支持的 博客文章。
For further background and context, you can also read the blog posts that introduced asynchronous request processing support in Spring MVC 3.2.
Exception Handling
当您使用 DeferredResult
时,您可以选择使用异常调用 setResult
或 setErrorResult
。在这两种情况下,Spring MVC 将请求分派回 Servlet 容器以完成处理。然后,它被视为控制器方法已返回给定值或好像它生成了给定异常。然后,异常会通过常规异常处理机制(例如,调用 @ExceptionHandler
方法)。
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.
The exception then goes through the regular exception handling mechanism (for example, invoking
@ExceptionHandler
methods).
当您使用 Callable
时,会发生类似的处理逻辑,主要区别在于结果是从 Callable
返回的,或者由它引发异常。
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.
Interception
HandlerInterceptor
实例可以是 AsyncHandlerInterceptor
类型,以在启动异步处理的初始请求上接收 afterConcurrentHandlingStarted
回调(而不是 postHandle
和 afterCompletion
)。
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
实现还可以注册 CallableProcessingInterceptor
或 DeferredResultProcessingInterceptor
,以更深入地集成到异步请求的生命周期(例如,处理超时事件)。查看 AsyncHandlerInterceptor
,了解更多详情。
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.
DeferredResult
提供了 onTimeout(Runnable)
和 onCompletion(Runnable)
回调。查看 DeferredResult
的 JavaDoc,了解更多详情。Callable
可以替换 WebAsyncTask
,它公开了用于超时和完成回调的其他方法。
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.
Async Spring MVC compared to WebFlux
Servlet API 最初是为通过 Filter-Servlet 链进行单次传递而构建的。异步请求处理允许应用程序退出 Filter-Servlet 链,但使响应保持打开状态以供进一步处理。Spring MVC 异步支持围绕该机制构建。当控制器返回 DeferredResult
时,Filter-Servlet 链退出,并且 Servlet 容器线程被释放。稍后,当设置 DeferredResult
时,会执行 ASYNC
分派(到同一个 URL),在过程中控制器再次映射,但不会调用它,而是使用 DeferredResult
值(好像控制器返回了它)恢复处理。
The Servlet API was originally built for making a single pass through the Filter-Servlet
chain. Asynchronous request processing lets applications exit the Filter-Servlet chain
but leave the response open for further processing. 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 WebFlux 既不是构建在 Servlet API 之上的,也不需要这样的异步请求处理特性,因为它在设计上就是异步的。异步处理被构建到所有框架约定中,并在请求处理的所有阶段得到本征支持。
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. Asynchronous handling is built into all framework contracts and is intrinsically supported through all stages of request processing.
从编程模型的角度来看,Spring MVC 和 Spring WebFlux 均支持在控制器方法中作为返回值的异步和 Reactive Types。Spring MVC 甚至支持流式处理,包括反应式反压。但是,与依赖于非阻塞 I/O 且不需要为每次写入开辟额外线程的 WebFlux 不同,对响应进行单独写入仍会阻塞(并会在单独的线程上执行)。
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. 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.
另一个根本区别是,Spring MVC 也不支持控制器方法参数中的异步或反应类型(例如,@RequestBody
、@RequestPart
等),也没有任何显式支持作为模型属性的异步和反应类型。Spring 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.
最后,从配置角度来看,必须 enabled at the Servlet container level 异步请求处理功能。
Finally, from a configuration perspective the asynchronous request processing feature must be enabled at the Servlet container level.
HTTP Streaming
对于单个异步返回值,可以使用 DeferredResult
和 Callable
。如果你想产生多个异步值,并且让它们被写入响应,该怎么做?此部分将说明如何进行此操作。
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.
Objects
您可以使用 ResponseBodyEmitter
返回值产生一个对象流,其中每个对象用 xref:integration/rest-clients.adoc#rest-message-conversion[HttpMessageConverter
序列化,并写入响应,如下例所示:
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:
-
Java
-
Kotlin
@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()
你还可以将 ResponseBodyEmitter
用作 ResponseEntity
中的主体,这使你能够自定义响应状态和标头。
You can also use ResponseBodyEmitter
as the body in a ResponseEntity
, letting you
customize the status and headers of the response.
当 emitter
引发 IOException
(例如,如果远程客户端消失),应用程序将不会负责清理连接,并且不应调用 emitter.complete
或 emitter.completeWithError
。相反,servlet 容器会自动启动 AsyncListener
错误通知,其中 Spring MVC 会调用 completeWithError
。然后,此调用会执行到应用程序的最后一次 ASYNC
派发,在此期间,Spring MVC 会调用配置的异常解析器并完成请求。
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.
SSE
SseEmitter
(ResponseBodyEmitter
的一个子类)提供对 Server-Sent Events 的支持,在此服务器发送的事件根据 W3C SSE 规范进行格式化。要从一个控制器产生一个 SSE 流,返回 SseEmitter
,如下例所示:
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:
-
Java
-
Kotlin
@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()
虽然 SSE 是用于在浏览器中进行流式传输的主要选项,但请注意 Internet Explorer 不支持服务器发送的事件。考虑与 SockJS fallback 传输(包括 SSE)结合使用 Spring 的 WebSocket messaging,这些传输针对范围广泛的浏览器。
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.
另请参阅 previous section 以获取有关异常处理的注意事项。
See also previous section for notes on exception handling.
Raw Data
有时,绕过消息转换并将流直接传输到响应 OutputStream
(例如,用于文件下载)非常有用。你可以使用 StreamingResponseBody
返回值类型来执行此操作,如下例所示:
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:
-
Java
-
Kotlin
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
@GetMapping("/download")
fun handle() = StreamingResponseBody {
// write...
}
可以将 StreamingResponseBody
用作 ResponseEntity
中的主体,以自定义响应的状态和标头。
You can use StreamingResponseBody
as the body in a ResponseEntity
to
customize the status and headers of the response.
Reactive Types
Spring MVC 支持在控制器中使用反应式客户端库(在 WebFlux 部分中也已读过 Reactive Libraries)。其中包括来自 spring-webflux
的 `WebClient`以及其他库,例如 Spring 数据反应式数据仓库。在这样的场景中,能够从控制器方法返回反应式类型非常方便。
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.
响应式返回值的处理如下:
Reactive return values are handled as follows:
-
A single-value promise is adapted to, similar to using
DeferredResult
. Examples includeMono
(Reactor) orSingle
(RxJava). -
A multi-value stream with a streaming media type (such as
application/x-ndjson
ortext/event-stream
) is adapted to, similar to usingResponseBodyEmitter
orSseEmitter
. Examples includeFlux
(Reactor) orObservable
(RxJava). Applications can also returnFlux<ServerSentEvent>
orObservable<ServerSentEvent>
. -
A multi-value stream with any other media type (such as
application/json
) is adapted to, similar to usingDeferredResult<List<?>>
.
Spring MVC 通过来自 |
Spring MVC supports Reactor and RxJava through the
|
对于流式处理响应,支持反应式反压,但对响应的写入仍然会阻塞,并通过 configured AsyncTaskExecutor
在单独的线程上运行,以避免阻塞上游源,例如来自 WebClient
的 Flux
。
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
AsyncTaskExecutor
, to avoid blocking the upstream source such as a Flux
returned
from WebClient
.
Context Propagation
通过 java.lang.ThreadLocal
传播上下文很常见。这对于在同一线程上进行处理是透明的,但对于跨多个线程的异步处理则需要额外的工作。Micrometer Context Propagation 库简化了跨线程以及跨上下文机制(例如 ThreadLocal
值)的上下文传播,Reactor context、GraphQL Java context 等等。
It is common to propagate context via java.lang.ThreadLocal
. This works transparently
for handling on the same thread, but requires additional work for asynchronous handling
across multiple threads. The Micrometer
Context Propagation
library simplifies context propagation across threads, and across context mechanisms such
as ThreadLocal
values,
Reactor context,
GraphQL Java context,
and others.
如果类路径上存在 Micrometer 上下文传播,当控制器方法返回 reactive type 例如 Flux
或 Mono
时,所有 ThreadLocal
值(已为其注册 io.micrometer.ThreadLocalAccessor
)都会使用由 ThreadLocalAccessor
分配的键作为键值对写入到 Reactor Context
。
If Micrometer Context Propagation is present on the classpath, when a controller method
returns a reactive type such as Flux
or Mono
, all
ThreadLocal
values, for which there is a registered io.micrometer.ThreadLocalAccessor
,
are written to the Reactor Context
as key-value pairs, using the key assigned by the
ThreadLocalAccessor
.
对于其他异步处理场景,可以直接使用 Context Propagation 库。例如:
For other asynchronous handling scenarios, you can use the Context Propagation library directly. For example:
// Capture ThreadLocal values from the main thread ...
ContextSnapshot snapshot = ContextSnapshot.captureAll();
// On a different thread: restore ThreadLocal values
try (ContextSnapshot.Scope scope = snapshot.setThreadLocals()) {
// ...
}
以下 ThreadLocalAccessor
实现是直接提供的:
The following ThreadLocalAccessor
implementations are provided out of the box:
-
LocaleContextThreadLocalAccessor
— propagatesLocaleContext
viaLocaleContextHolder
-
RequestAttributesThreadLocalAccessor
— propagatesRequestAttributes
viaRequestContextHolder
以上内容不会自动注册。你需要在启动时通过 ContextRegistry.getInstance()
注册它们。
The above are not registered automatically. You need to register them via ContextRegistry.getInstance()
on startup.
有关更多详细信息,请参阅 Micrometer 上下文传播库的 documentation。
For more details, see the documentation of the Micrometer Context Propagation library.
Disconnects
Servlet API 不会在远程客户端消失时提供任何通知。因此,在通过 SseEmitter或 reactive types对响应进行流式处理时,定期发送数据非常重要,因为如果客户端已断开连接,则写入操作将失败。发送可以采用空 (仅注释) SSE 事件或任何其他数据形式,接收方必将将其解释为心跳并予以忽略。
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.
或者,考虑使用具有内置心跳机制的网络消息传递解决方案(例如 STOMP over WebSocket 或与 SockJS 结合使用的 WebSocket)。
Alternatively, consider using web messaging solutions (such as STOMP over WebSocket or WebSocket with SockJS) that have a built-in heartbeat mechanism.
Configuration
必须在 Servlet 容器级别启用异步请求处理功能。MVC 配置也为异步请求提供了多个选项。
The asynchronous request processing feature must be enabled at the Servlet container level. The MVC configuration also exposes several options for asynchronous requests.
Servlet Container
Filter 和 Servlet 声明具有 asyncSupported
标志,而该标志需要设为 true
以启用异步请求处理。此外,应声明 Filter 映射以处理 ASYNC
jakarta.servlet.DispatchType
。
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
jakarta.servlet.DispatchType
.
在 Java 配置中,使用 AbstractAnnotationConfigDispatcherServletInitializer
初始化 Servlet 容器时,将会自动完成此操作。
In Java configuration, when you use AbstractAnnotationConfigDispatcherServletInitializer
to initialize the Servlet container, this is done automatically.
在 web.xml
配置中,可以向 DispatcherServlet
和 Filter
声明中添加 <async-supported>true</async-supported>
,并向过滤器映射中添加 <dispatcher>ASYNC</dispatcher>
。
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.
Spring MVC
MVC 配置为异步请求处理提供了以下选项:
The MVC configuration exposes the following options for asynchronous request processing:
-
Java configuration: Use the
configureAsyncSupport
callback onWebMvcConfigurer
. -
XML namespace: Use the
<async-support>
element under<mvc:annotation-driven>
.
可以配置以下内容:
You can configure the following:
-
The default timeout value for async requests depends on the underlying Servlet container, unless it is set explicitly.
-
AsyncTaskExecutor
to use for blocking writes when streaming with Reactive Types and for executingCallable
instances returned from controller methods. The one used by default is not suitable for production under load. -
DeferredResultProcessingInterceptor
implementations andCallableProcessingInterceptor
implementations.
请注意,还可以在 DeferredResult
、ResponseBodyEmitter
和 SseEmitter
上设置默认超时值。对于 Callable
,可以使用 WebAsyncTask
提供超时值。
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.