异步请求
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设置后,会触发一个ASYNCdispatch(到同一 URL),在此期间,
controller 再次映射,但DeferredResultvalue 被使用
(就像控制器返回一样)以恢复处理。
相比之下,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对于单个异步返回值。
如果您想生成多个异步值,并将这些值写入
响应?本节介绍如何执行此作。
对象
您可以使用ResponseBodyEmitterreturn 值来生成对象流,其中
每个对象都使用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叫。
此调用反过来会执行最后一个ASYNCdispatch 到应用程序,在此期间 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(例如,对于文件下载)。您可以使用StreamingResponseBodyreturn 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()) {
// ...
}
以下内容ThreadLocalAccessor实现是开箱即用的:
-
LocaleContextThreadLocalAccessor— 传播LocaleContext通过LocaleContextHolder -
RequestAttributesThreadLocalAccessor— 传播RequestAttributes通过RequestContextHolder
以上内容不会自动注册。您需要通过以下方式注册它们ContextRegistry.getInstance()启动时。
断开
当远程客户端消失时,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.xmlconfiguration 中,您可以添加<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 不适合在负载下生产。 -
DeferredResultProcessingInterceptorimplementations 和CallableProcessingInterceptor实现。
请注意,您还可以在DeferredResult,
一个ResponseBodyEmitter和SseEmitter.对于Callable,您可以使用WebAsyncTask以提供超时值。