此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
异步请求
Spring MVC 与 Servlet 异步请求处理进行了广泛的集成:
有关它与 Spring WebFlux 有何不同的概述,请参阅下面的异步 Spring MVC 与 WebFlux 的比较部分。
DeferredResult
在 Servlet 容器中启用异步请求处理功能后,控制器方法可以包装任何受支持的控制器方法
return value 替换为DeferredResult
,如下例所示:
-
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 消息)、计划任务或其他事件。
Callable
控制器可以用java.util.concurrent.Callable
,
如下例所示:
-
Java
-
Kotlin
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return () -> "someView";
}
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
// ...
"someView"
}
然后,可以通过配置的 AsyncTaskExecutor
.
加工
以下是 Servlet 异步请求处理的非常简洁的概述:
-
一个
ServletRequest
可以通过调用request.startAsync()
. 这样做的主要效果是 Servlet(以及任何过滤器)可以退出,但 响应将保持打开状态,以便稍后完成处理。 -
对
request.startAsync()
返回AsyncContext
,您可以将其用于 进一步控制异步处理。例如,它提供dispatch
方法 它类似于 Servlet API 的 forward,不同之处在于它允许 应用程序在 Servlet 容器线程上恢复请求处理。 -
这
ServletRequest
提供对当前DispatcherType
,您可以 用于区分处理初始请求、异步 dispatch、a forward 和其他 Dispatcher 类型。
DeferredResult
处理工作如下:
-
控制器返回一个
DeferredResult
并将其保存在内存中 queue 或 list 中。 -
Spring MVC 调用
request.startAsync()
. -
与此同时,
DispatcherServlet
,并且所有已配置的过滤器都会退出请求 processing 线程,但响应保持打开状态。 -
应用程序将
DeferredResult
来自某个线程,以及 Spring MVC 将请求分派回 Servlet 容器。 -
这
DispatcherServlet
,并且处理会恢复,并显示 异步生成的返回值。
Callable
处理工作如下:
-
控制器返回一个
Callable
. -
Spring MVC 调用
request.startAsync()
并提交Callable
自 一AsyncTaskExecutor
以便在单独的线程中进行处理。 -
与此同时,
DispatcherServlet
并且所有过滤器都退出 Servlet 容器线程, 但回应仍然开放。 -
最终,
Callable
产生一个结果,Spring MVC 将请求分派回来 添加到 Servlet 容器中以完成处理。 -
这
DispatcherServlet
,并且处理会恢复,并显示 从Callable
.
有关更多背景和上下文,您还可以阅读 在 Spring MVC 3.2 中引入异步请求处理支持的博客文章。
异常处理
当您使用DeferredResult
,您可以选择是否调用setResult
或setErrorResult
但有一个例外。在这两种情况下,Spring MVC 都会将请求分派回来
添加到 Servlet 容器中以完成处理。然后,它被视为
controller 方法返回给定的值,或者就像它产生了给定的异常一样。
然后,异常会通过常规的异常处理机制(例如,调用@ExceptionHandler
方法)。
当您使用Callable
,则会出现类似的处理逻辑,主要区别在于
结果从Callable
或由它引发异常。
拦截
HandlerInterceptor
实例可以是AsyncHandlerInterceptor
,以接收afterConcurrentHandlingStarted
对启动异步的初始请求的 callback
正在处理(而不是postHandle
和afterCompletion
).
HandlerInterceptor
实现还可以注册一个CallableProcessingInterceptor
或DeferredResultProcessingInterceptor
,以便与
异步请求的生命周期(例如,处理超时事件)。看AsyncHandlerInterceptor
了解更多详情。
DeferredResult
提供onTimeout(Runnable)
和onCompletion(Runnable)
回调。
请参阅javadoc 的DeferredResult
了解更多详情。Callable
可以替代WebAsyncTask
这会暴露额外的
timeout和completion回调的方法。
异步 Spring MVC 与 WebFlux 的比较
Servlet API 最初是为通过 Filter-Servlet 进行一次传递而构建的
链。异步请求处理允许应用程序退出 Filter-Servlet 链
但将响应保留为打开状态以供进一步处理。Spring MVC 异步支持
是围绕该机制构建的。当控制器返回DeferredResult
这
退出 Filter-Servlet 链,释放 Servlet 容器线程。稍后,当
这DeferredResult
设置后,会触发一个ASYNC
dispatch(到同一 URL),在此期间,
controller 再次映射,但DeferredResult
value 被使用
(就像控制器返回一样)以恢复处理。
相比之下,Spring WebFlux 既不是基于 Servlet API 构建的,也不需要这样的 asynchronous request processing 功能,因为它在设计上是异步的。异步 Handling 内置于所有框架 Contract 中,并且通过 ALL 请求处理阶段。
从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持 asynchronous 和 Reactive Types 作为控制器方法中的返回值。 Spring MVC 甚至支持流,包括反应式背压。但是,单个 对响应的写入仍然是阻塞的(并且在单独的线程上执行),这与 WebFlux 不同, 它依赖于非阻塞 I/O,并且每次写入不需要额外的线程。
另一个根本区别是 Spring MVC 不支持异步或反应式
控制器方法参数中的类型(例如@RequestBody
,@RequestPart
等)、
它也没有任何明确支持异步和反应类型作为模型属性。
Spring WebFlux 确实支持所有这些。
最后,从配置的角度来看,必须在 Servlet 容器级别启用异步请求处理功能。
HTTP 流式处理
您可以使用DeferredResult
和Callable
对于单个异步返回值。
如果您想生成多个异步值,并将这些值写入
响应?本节介绍如何执行此作。
对象
您可以使用ResponseBodyEmitter
return 值来生成对象流,其中
每个对象都使用HttpMessageConverter
并写入
响应,如下例所示:
-
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
,让您
自定义响应的状态和标头。
当emitter
抛出一个IOException
(例如,如果远程客户端消失)、应用程序
不负责清理连接,不应调用emitter.complete
或emitter.completeWithError
.相反,Servlet 容器会自动启动AsyncListener
错误通知,其中 Spring MVC 会创建一个completeWithError
叫。
此调用反过来会执行最后一个ASYNC
dispatch 到应用程序,在此期间 Spring MVC
调用配置的异常解析程序并完成请求。
上交所
SseEmitter
(ResponseBodyEmitter
) 提供对 Server-Sent Events 的支持,其中从服务器发送的事件
根据 W3C SSE 规范进行格式设置。生成 SSE
stream 中,返回SseEmitter
,如下例所示:
-
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 不支持 Server-Sent Events。考虑将 Spring 的 WebSocket 消息传递与 Sockjs 回退传输(包括 SSE)一起使用,该传输将 广泛的浏览器。
有关异常处理的说明,另请参阅上一节。
原始数据
有时,绕过消息转换并直接流式传输到响应很有用OutputStream
(例如,对于文件下载)。您可以使用StreamingResponseBody
return value 类型来执行此作,如下例所示:
-
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
自
自定义响应的状态和标头。
响应式类型
Spring MVC 支持在控制器中使用反应式 Client 端库(另请阅读 WebFlux 部分中的反应式库)。
这包括WebClient
从spring-webflux
和其他
反应式数据存储库。在这种情况下,能够返回很方便
来自 controller 方法的 reactive 类型。
反应式返回值的处理方式如下:
-
单值 promise 适应,类似于使用
DeferredResult
.例子 包括Mono
(Reactor) 或Single
(RxJava) 的 Java 版本。 -
具有流媒体类型(如
application/x-ndjson
或text/event-stream
) 适应于,类似于使用ResponseBodyEmitter
或SseEmitter
.示例包括Flux
(Reactor) 或Observable
(RxJava) 的 Java 版本。 应用程序也可以返回Flux<ServerSentEvent>
或Observable<ServerSentEvent>
. -
具有任何其他媒体类型(例如
application/json
) 已适应 to 的 API API 中,类似于使用DeferredResult<List<?>>
.
Spring MVC 通过ReactiveAdapterRegistry 从spring-core ,这允许它适应多个响应式库。 |
对于流式传输到响应,支持反应式背压,但会写入
响应仍然阻塞,并通过配置的
AsyncTaskExecutor
,以避免阻塞上游源(例如Flux
返回
从WebClient
.
上下文传播
通常通过java.lang.ThreadLocal
.这是透明的
用于同一线程上的处理,但需要额外的异步处理工作
跨多个线程。Micrometer Context Propagation 库简化了跨线程和跨上下文机制的上下文传播,例如
如ThreadLocal
值
反应器上下文 /
GraphQL Java 上下文、
和其他。
如果 Micrometer Context Propagation 存在于 Classpath 上,则当控制器方法
返回一个反应式类型,例如Flux
或Mono
都ThreadLocal
值,其中有一个已注册的io.micrometer.ThreadLocalAccessor
,
写入 ReactorContext
作为键值对,使用ThreadLocalAccessor
.
对于其他异步处理场景,您可以使用 Context Propagation 库 径直。例如:
// Capture ThreadLocal values from the main thread ...
ContextSnapshot snapshot = ContextSnapshot.captureAll();
// On a different thread: restore ThreadLocal values
try (ContextSnapshot.Scope scope = snapshot.setThreadLocals()) {
// ...
}
断开
当远程客户端消失时,Servlet API 不提供任何通知。 因此,在流式传输到响应时,无论是通过 SseEmitter 还是反应式类型,定期发送数据都很重要。 因为如果客户端已断开连接,则写入失败。发送可以采用 空(仅评论)SSE 事件或另一方必须解释的任何其他数据 作为心跳并忽略。
或者,考虑使用 Web 消息传递解决方案(例如基于 WebSocket 的 STOMP 或带有 SockJS 的 WebSocket) 具有内置心跳机制的 S S S 的 S S S 的 S T
配置
必须在 Servlet 容器级别启用异步请求处理功能。 MVC 配置还为异步请求公开了几个选项。
Servlet 容器
Filter 和 Servlet 声明具有asyncSupported
标志,需要设置为true
以启用异步请求处理。此外,Filter 映射应为
declared 来处理ASYNC
jakarta.servlet.DispatchType
.
在 Java 配置中,当您使用AbstractAnnotationConfigDispatcherServletInitializer
初始化 Servlet 容器,此作会自动完成。
在web.xml
configuration 中,您可以添加<async-supported>true</async-supported>
到DispatcherServlet
以及Filter
声明并添加<dispatcher>ASYNC</dispatcher>
以筛选映射。
Spring MVC
MVC 配置公开了以下用于异步请求处理的选项:
-
Java 配置:使用
configureAsyncSupport
回调开启WebMvcConfigurer
. -
XML 命名空间:使用
<async-support>
元素<mvc:annotation-driven>
.
您可以配置以下内容:
-
异步请求的默认超时值取决于 在底层 Servlet 容器上,除非它被显式设置。
-
AsyncTaskExecutor
用于在使用 Reactive Types 进行流式处理时阻止写入,以及 执行Callable
从 Controller 方法返回的实例。 默认使用的 SQL 不适合在负载下生产。 -
DeferredResultProcessingInterceptor
implementations 和CallableProcessingInterceptor
实现。
请注意,您还可以在DeferredResult
,
一个ResponseBodyEmitter
和SseEmitter
.对于Callable
,您可以使用WebAsyncTask
以提供超时值。