|
对于最新稳定版本,请使用Spring Framework 7.0.1! |
异步请求
推迟结果
一旦在 Servlet 容器中启用异步请求处理功能,控制器方法就可以对任何支持的控制器方法进行封装
返回值为推迟结果,如下示例所示:
-
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 消息)、调度任务或其他事件。
调用
控制器可以将任何支持的返回值包裹为java.util.concurrent.Callable,
如下示例所示:
-
Java
-
Kotlin
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return () -> "someView";
}
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
// ...
"someView"
}
返回值可以通过将指定任务运行到配置中的 任务执行者.
加工
以下是Servlet异步请求处理的非常简明的概述:
-
一个
ServletRequest可以通过调用来进入异步模式request.startAsync(). 这样做的主要效果是 Servlet(以及任何Filter)可以退出,但 响应保持开放状态,等待处理完成。 -
召唤
request.startAsync()返回异步上下文,你可以用它来表示 对异步处理的进一步控制。例如,它提供了遣方法 这类似于从 Servlet API 转发的作,但允许 应用程序在 Servlet 容器线程上恢复请求处理。 -
这
ServletRequest提供通往水流的通道Dispatcher类型,你可以 用于区分处理初始请求和异步请求 调度员、前拨调度员及其他调度员类型。
推迟结果处理过程如下:
-
控制器返回
推迟结果并将其保存在某个内存中 在可访问的位置排队或列表。 -
春季MVC呼叫
request.startAsync(). -
与此同时,
调度器服务所有配置的过滤器退出请求 处理线程,但响应依然开放。 -
应用程序设置
推迟结果来自某个帖子,以及春季MVC 将请求返回 Servlet 容器。 -
这
调度器服务再次被调用,处理程序继续进行 异步生成的返回价值。
调用处理过程如下:
-
控制器返回
调用. -
春季MVC呼叫
request.startAsync()并提交调用自 一个任务执行者用于在独立线程中处理。 -
与此同时,
调度器服务所有过滤器都退出Servlet容器线程, 但回应仍未定论。 -
最终
调用产生结果后,Spring MVC 会返回请求 传输到Servlet容器以完成处理。 -
这
调度器服务再次被调用,处理程序继续进行 异步生成的返回值从调用.
关于更多背景和背景,你也可以阅读 在 Spring MVC 3.2 中引入异步请求处理支持的博客文章。
异常处理
当你使用推迟结果你可以选择是否打电话setResult或setErrorResult但有一个例外。在这两种情况下,Spring MVC 都会将请求返回
传输到Servlet容器以完成处理。然后将其视为
控制器方法返回给定值,或仿佛产生了给定的异常。
异常随后通过常规的异常处理机制(例如调用@ExceptionHandler方法)。
当你使用调用,处理逻辑类似,主要区别是
结果由 返回调用或者被提出例外。
拦截
拦截者实例可以是 类型异步处理拦截者,以接收afterConcurrentHandlingStarted对异步开始的初始请求回调
处理(代替postHandle和完工后).
拦截者实现也可以注册 a可调用处理拦截器或者延迟结果处理拦截器,以便更深入地与
异步请求的生命周期(例如,处理超时事件)。看异步处理拦截者更多细节请阅读。
推迟结果提供ontimeout(可跑)和onCompletion(可跑)回调。
参见Java doc 的推迟结果更多细节请阅读。调用可以替换为WebAsync任务这会暴露出更多
超时和完成回调的方法。
异步 Spring MVC 与 WebFlux 的比较
Servlet API 最初是为对过滤 Servlet 进行一次处理而构建的
链。异步请求处理允许应用程序退出过滤器-Servlet链
但请留待进一步处理。Spring MVC 异步支持
是围绕这个机制构建的。当控制器返回推迟结果这
Filter-Servlet 链被退出,Servlet 容器线程被释放。以后,当
这推迟结果是设定的,一个异步发送(到同一URL)时,在此过程中
控制器再次映射,但不是调用它,而是推迟结果使用值
(好像控制器还回了一样)以恢复处理。
相比之下,Spring WebFlux 既不是基于 Servlet API,也不需要这样的 异步请求处理功能,因为它设计上就是异步的。异步 处理功能内置于所有框架合同中,并且在所有框架合同中得到内在支持 请求处理的各个阶段。
从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持控制器方法中的异步类型和响应式作为返回值。Spring MVC 甚至支持流式传输,包括响应式背压。然而,单个对响应的写入仍然是阻塞的(并且在独立的线程上执行),与 WebFlux 不同,WebFlux 依赖非阻塞的 I/O,且每次写入不需要额外线程。
另一个根本区别是 Spring MVC 不支持异步或响应式控制器方法参数中的类型(例如,@RequestBody,@RequestPart, 以及其他),它也没有显式支持异步和响应式类型作为模型属性。Spring WebFlux 支持所有这些。
最后,从配置角度来看,异步请求处理功能必须在 Servlet 容器层面启用。
HTTP 流式传输
你可以使用推迟结果和调用对于单个异步返回值。如果你想生成多个异步值,并且把它们写入 响应? 本节将介绍如何实现。
对象
你可以使用ResponseBodyEmitter返回值以产生一组对象流,其中每个对象都被序列化为HttpMessage转换器并写入响应,如下示例所示:
-
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作为 a 中的身体响应实体, 允许您自定义响应的状态和头部。
当发射抛出IOException(例如,如果远程客户端消失),应用程序不负责清理连接,也不应调用发射器。完成或emitter.completeWithError(出错). 相反,servlet 容器会自动启动异步监听器错误通知,其中 Spring MVC 会完成与错误叫。 该呼叫随后执行最后一次异步调度到应用程序,在此期间 Spring MVC调用配置的异常解析器并完成请求。
SSE
发射器(一个子类ResponseBodyEmitter)支持服务器发送事件,即服务器发送的事件按照W3C SSE规范格式化。要生成SSE来自控制器的流,返回发射器,如下示例所示:
-
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不支持服务器发送事件。考虑使用 Spring 的 WebSocket 消息,配合 SockJS 的备援传输(包括 SSE),这些传输针对广泛的浏览器。
另见前一节关于异常处理的说明。
原始数据
有时,绕过消息转换直接流向响应是有用的输出流(例如,用于文件下载)。你可以使用StreamingResponseBody返回值类型以实现这一点,如下示例所示:
-
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作为 a 中的身体响应实体自 自定义回复的状态和头部。
反应类型
Spring MVC 支持在控制器中使用响应式客户端库(也请阅读 WebFlux 部分的响应式库)。这包括Web客户端从春季网流以及其他,如Spring Data响应式数据仓库。在这种情况下,能够返回控制器方法中的反应类型非常方便。
反应返回值的处理方式如下:
-
单值承诺被调整为 ,类似于
推迟结果. 例子 包括单(反应堆)或单(RxJava)。 -
具有流媒体类型的多值流(例如:
application/x-ndjson或文本/事件流)被改编为,类似于使用ResponseBodyEmitter或发射器. 例子包括通量(反应堆)或观察(RxJava)。应用程序也可以返回Flux<ServerSentEvent>或Observable<ServerSentEvent>. -
具有任意其他媒体类型的多值流(例如
application/json)是被改编的,类似于使用DeferredResult<List<?>>.
Spring MVC 通过ReactiveAdapter注册表从Spring芯,使其能够从多个响应式库中进行自适应。 |
上下文传播
通常通过以下方式传播上下文java.lang.ThreadLocal. 这在透明情况下是在同一线程上处理,但异步处理则需要额外工作跨多个线程。Micrometer 上下文传播库简化了线程间的上下文传播,以及跨上下文机制,例如 如ThreadLocal值 反应堆上下文,GraphQL Java 上下文,以及其他。
如果类路径上存在微米上下文传播,当控制器方法返回反应类型,例如通量或单都ThreadLocal对于 有注册值的值io.micrometer.ThreadLocalAccessor, 写入反应堆上下文作为键值对,使用由线程本地访问器.
对于其他异步处理场景,您可以使用上下文传播库 径直。 例如:
// 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 事件或其他数据,对方需要将其解读为心跳并忽略。
或者,考虑使用带有心跳机制的网页消息解决方案(如 STOMP over WebSocket 或 WebSocket with SockJS)。这些解决方案内置了心跳机制。
配置
异步请求处理功能必须在 Servlet 容器层面启用。MVC 配置还暴露了多种异步请求选项。
Servlet 容器
Filter和 Servlet 声明具有asyncSupported需要设置为的标志true以实现异步请求处理。此外,过滤映射应被声明以处理异步 jakarta.servlet.DispatchType.
在 Java 配置中,当你使用AbstractAnnotationConfigDispatcherServletInitializer要初始化 Servlet 容器,则是自动完成的。
在web.xml配置,你可以添加<async-supported>true</async-supported>前往调度器服务并且Filter声明和 add<调度员>ASYNC</调度员>用于筛选映射。
春季MVC
MVC 配置揭示了以下与异步请求处理相关的选项:
-
Java 配置:使用
configureAsyncSupport致敬WebMvcConfigurer. -
XML 命名空间:使用
<异步支持>下 元素<mvc:annotation-driven>.
您可以配置以下内容:
-
异步请求的默认超时值,如果未设置,则依赖于底层的 Servlet 容器。
-
异步任务执行器用于在使用反应类型流式传输时阻断写入,以及执行调用实例返回于控制器方法。我们强烈建议配置该属性,如果你使用 reactive 类型的流,或者有返回的控制器方法。调用因为 默认情况下,它是SimpleAsyncTaskExecutor. -
延迟结果处理拦截器实现和可调用处理拦截器实现。
注意,你也可以设置默认超时值推迟结果, 一个ResponseBodyEmitter,以及一个发射器. 对于一个调用,你可以使用WebAsync任务以提供超时值。