Spring MVC 与 Servlet 异步请求处理进行了广泛的集成:
有关这与 Spring WebFlux 有何不同的概述,请参阅下面的异步 Spring MVC 与 WebFlux 的比较部分。
DeferredResult
在 Servlet 容器中启用异步请求处理功能后,控制器方法可以包装任何支持的控制器方法
返回 的值,如以下示例所示: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)
控制器可以从不同的线程异步生成返回值 — for 例如,响应外部事件(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 异步请求处理的非常简洁的概述:
-
可以通过调用 来将 A 置于异步模式。 这样做的主要效果是 Servlet(以及任何过滤器)可以退出,但 响应将保持打开状态,以便稍后完成处理。
ServletRequest
request.startAsync()
-
对 的调用返回 ,可用于 进一步控制异步处理。例如,它提供了方法, 它类似于 Servlet API 的转发,只是它允许 在 Servlet 容器线程上处理应用程序恢复请求。
request.startAsync()
AsyncContext
dispatch
-
提供对当前 的访问,您可以 用于区分处理初始请求、异步请求 dispatch、a forward 和其他调度程序类型。
ServletRequest
DispatcherType
DeferredResult
处理工作如下:
-
控制器返回 a 并将其保存在内存中 可以访问它的队列或列表。
DeferredResult
-
Spring MVC 调用 .
request.startAsync()
-
同时,所有配置的过滤器退出请求 处理线程,但响应保持打开状态。
DispatcherServlet
-
应用程序设置 from 某个线程和 Spring MVC 将请求调度回 Servlet 容器。
DeferredResult
-
再次调用 ,并使用 异步生成的返回值。
DispatcherServlet
Callable
处理工作如下:
-
控制器返回 .
Callable
-
Spring MVC 调用并提交到 用于在单独的线程中进行处理。
request.startAsync()
Callable
AsyncTaskExecutor
-
同时,和 所有过滤器退出 Servlet 容器线程, 但回应仍然悬而未决。
DispatcherServlet
-
最终生成一个结果,Spring MVC 将请求调回 到 Servlet 容器以完成处理。
Callable
-
再次调用 ,并使用 异步生成的返回值。
DispatcherServlet
Callable
有关更多背景和上下文,您还可以阅读 在 Spring MVC 3.2 中引入异步请求处理支持的博客文章。
异常处理
使用 时,可以选择是调用还是使用异常。在这两种情况下,Spring MVC 都会将请求调回
到 Servlet 容器以完成处理。然后将其视为
controller 方法返回给定值,或者好像它产生了给定的异常。
然后,异常将通过常规异常处理机制(例如,调用方法)。DeferredResult
setResult
setErrorResult
@ExceptionHandler
当您使用 时,会出现类似的处理逻辑,主要区别在于
结果从 或 或 引发异常。Callable
Callable
拦截
HandlerInterceptor
实例的类型可以是 ,用于接收异步启动的初始请求的回调
处理(而不是 和 )。AsyncHandlerInterceptor
afterConcurrentHandlingStarted
postHandle
afterCompletion
HandlerInterceptor
实现也可以注册 A 或 A ,以便更深入地与
异步请求的生命周期(例如,处理超时事件)。有关详细信息,请参阅 AsyncHandlerInterceptor
。CallableProcessingInterceptor
DeferredResultProcessingInterceptor
DeferredResult
提供和回调。
有关更多详细信息,请参阅 DeferredResult
的 javadoc。 可以代替暴露额外的
超时和完成回调的方法。onTimeout(Runnable)
onCompletion(Runnable)
Callable
WebAsyncTask
异步 Spring MVC 与 WebFlux 的比较
Servlet API 最初是为通过 Filter-Servlet 进行单次传递而构建的
链。异步请求处理允许应用程序退出 Filter-Servlet 链
但请保留响应以供进一步处理。Spring MVC 异步支持
是围绕该机制构建的。当控制器返回 时,
退出 Filter-Servlet 链,并释放 Servlet 容器线程。后来,当
设置,进行调度(到同一 URL),在此期间
控制器再次映射,但不是调用它,而是使用该值
(就好像控制器返回了它一样)以恢复处理。DeferredResult
DeferredResult
ASYNC
DeferredResult
相比之下,Spring WebFlux 既不是基于 Servlet API 构建的,也不需要这样的 异步请求处理功能,因为它在设计上是异步的。异步 处理内置于所有框架合同中,并通过所有框架合同获得内在支持 请求处理的阶段。
从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持 asynchronous 和 Reactive Types 作为控制器方法中的返回值。 Spring MVC 甚至支持流式处理,包括无功背压。但是,个人 与 WebFlux 不同,对响应的写入仍然阻塞(并在单独的线程上执行), 它依赖于非阻塞 I/O,并且每次写入都不需要额外的线程。
另一个根本区别是 Spring MVC 不支持异步或反应式
控制器方法参数中的类型(例如、、 等),
它也没有明确支持异步和反应式类型作为模型属性。
Spring WebFlux 确实支持所有这些。@RequestBody
@RequestPart
最后,从配置的角度来看,必须在 Servlet 容器级别启用异步请求处理功能。
HTTP 流
可以将 和 用于单个异步返回值。
如果要生成多个异步值并将这些值写入
响应?本节介绍如何执行此操作。DeferredResult
Callable
对象
您可以使用返回值生成对象流,其中
每个对象都使用 HttpMessageConverter
序列化,并写入
响应,如以下示例所示:ResponseBodyEmitter
-
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
当 an 抛出 (例如,如果远程客户端消失), 应用程序
不负责清理连接,也不应调用 或 。相反,servlet 容器会自动启动错误通知,Spring MVC 会在其中进行调用。
反过来,此调用会对应用程序执行最后一次调度,在此期间,Spring MVC
调用配置的异常解析程序并完成请求。emitter
IOException
emitter.complete
emitter.completeWithError
AsyncListener
completeWithError
ASYNC
上交所
SseEmitter
() 的子类提供对服务器发送事件的支持,其中从服务器发送的事件
根据 W3C SSE 规范进行格式化。生成 SSE
从控制器流,返回 ,如以下示例所示:ResponseBodyEmitter
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 不支持服务器发送的事件。考虑将 Spring 的 WebSocket 消息传递与 SockJS 回退传输(包括 SSE)一起使用 广泛的浏览器。
有关异常处理的说明,另请参阅上一节。
原始数据
有时,绕过消息转换并直接流式传输到响应(例如,对于文件下载)很有用。可以使用返回值类型来执行此操作,如以下示例所示:OutputStream
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...
}
你可以在 a 中使用作为身体
自定义响应的状态和标头。StreamingResponseBody
ResponseEntity
反应类型
Spring MVC 支持在控制器中使用反应式客户端库(另请阅读 WebFlux 部分中的响应式库)。
这包括 from 和其他,例如 Spring Data
反应式数据存储库。在这种情况下,能够返回很方便
控制器方法中的反应式类型。WebClient
spring-webflux
反应式返回值的处理方式如下:
-
适应单值 promise 类似于使用 .例子 include (Reactor) 或 (RxJava)。
DeferredResult
Mono
Single
-
具有流媒体类型(如 或)的多值流适用于,类似于使用 或 。示例包括 (Reactor) 或 (RxJava)。 应用程序也可以返回 或 .
application/x-ndjson
text/event-stream
ResponseBodyEmitter
SseEmitter
Flux
Observable
Flux<ServerSentEvent>
Observable<ServerSentEvent>
-
适用于具有任何其他媒体类型(如 )的多值流 to,类似于使用 .
application/json
DeferredResult<List<?>>
Spring MVC 通过 的 ReactiveAdapterRegistry 支持 Reactor 和 RxJava,这让它能够适应多个反应式库。spring-core |
对于流式传输到响应,支持无功背压,但写入
响应仍然阻塞,并且通过配置在单独的线程上运行,以避免阻塞上游源,例如返回的
从。AsyncTaskExecutor
Flux
WebClient
上下文传播
通常通过 传播上下文。这是透明的
用于在同一线程上进行处理,但需要额外的异步处理工作
跨多个线程。Micrometer Context Propagation 库简化了跨线程和跨上下文机制的上下文传播,例如
作为值,
反应器环境,
GraphQL Java上下文,
和其他人。java.lang.ThreadLocal
ThreadLocal
如果类路径上存在 Micrometer Context Propagation,则当控制器方法
返回一个反应式类型,例如 或 ,所有值,其已注册 ,
作为键值对写入 Reactor,使用 .Flux
Mono
ThreadLocal
io.micrometer.ThreadLocalAccessor
Context
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 事件或另一方必须解释的任何其他数据 作为心跳和忽略。
或者,考虑使用 Web 消息传递解决方案(例如 STOMP over WebSocket 或 WebSocket with SockJS) 具有内置的心跳机制。
配置
必须在 Servlet 容器级别启用异步请求处理功能。 MVC 配置还公开了异步请求的多个选项。
Servlet 容器
Filter 和 Servlet 声明具有一个标志,需要将其设置为启用异步请求处理。此外,筛选器映射应为
声明处理 .asyncSupported
true
ASYNC
jakarta.servlet.DispatchType
在 Java 配置中,当您用于初始化 Servlet 容器时,这是自动完成的。AbstractAnnotationConfigDispatcherServletInitializer
在配置中,可以添加到 and 声明和添加到筛选器映射。web.xml
<async-supported>true</async-supported>
DispatcherServlet
Filter
<dispatcher>ASYNC</dispatcher>
Spring MVC
MVC 配置公开了以下用于异步请求处理的选项:
-
Java 配置:使用 回调 。
configureAsyncSupport
WebMvcConfigurer
-
XML 命名空间:使用 .
<async-support>
<mvc:annotation-driven>
您可以配置以下内容:
-
异步请求的默认超时值取决于 在底层 Servlet 容器上,除非它是显式设置的。
-
AsyncTaskExecutor
用于在使用反应式类型进行流式传输时阻止写入,以及用于 执行从控制器方法返回的实例。 默认使用的那个不适合在负载下生产。Callable
-
DeferredResultProcessingInterceptor
实现和实现。CallableProcessingInterceptor
请注意,您还可以在 上设置默认超时值 ,
a 和 .对于 ,您可以使用 来提供超时值。DeferredResult
ResponseBodyEmitter
SseEmitter
Callable
WebAsyncTask