此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
异步请求
Spring MVC 与 Servlet 异步请求处理进行了广泛的集成:
-
DeferredResult
,Callable
和WebAsyncTask
返回值 in 控制器方法提供对单个异步返回值的支持。 -
控制器可以使用响应式客户端并返回响应式类型进行响应处理。
有关这与 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)
控制器可以从不同的线程异步生成返回值 — 对于 例如,响应外部事件(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
.
WebAsyncTask
WebAsyncTask
与使用 Callable 相当,但允许自定义其他设置,例如请求超时值和AsyncTaskExecutor
执行java.util.concurrent.Callable
用代替
为 Spring MVC 全局设置的默认值。下面是使用WebAsyncTask
:
-
Java
-
Kotlin
@GetMapping("/callable")
WebAsyncTask<String> handle() {
return new WebAsyncTask<String>(20000L,()->{
Thread.sleep(10000); //simulate long-running task
return "asynchronous request completed";
});
}
@GetMapping("/callable")
fun handle(): WebAsyncTask<String> {
return WebAsyncTask(20000L) {
Thread.sleep(10000) // simulate long-running task
"asynchronous request completed"
}
}
加工
以下是 Servlet 异步请求处理的非常简明的概述:
-
一个
ServletRequest
可以通过调用request.startAsync()
. 这样做的主要效果是 Servlet(以及任何过滤器)可以退出,但是 响应保持打开状态,以便稍后完成处理。 -
呼吁
request.startAsync()
返回AsyncContext
,您可以将其用于 进一步控制异步处理。例如,它提供了dispatch
方法 这类似于 Servlet API 的转发,不同之处在于它允许 应用程序恢复 Servlet 容器线程上的请求处理。 -
这
ServletRequest
提供对当前DispatcherType
,您可以 用于区分处理初始请求、异步请求 dispatch、forward 和其他调度程序类型。
DeferredResult
处理工作如下:
-
控制器返回一个
DeferredResult
并将其保存在内存中 可以访问它的队列或列表。 -
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
异步启动的初始请求的回调
processing(而不是postHandle
和afterCompletion
).
HandlerInterceptor
实现还可以注册一个CallableProcessingInterceptor
或DeferredResultProcessingInterceptor
,以更深入地集成
异步请求的生命周期(例如,处理超时事件)。看AsyncHandlerInterceptor
了解更多详情。
DeferredResult
提供onTimeout(Runnable)
和onCompletion(Runnable)
回调。
请参阅javadoc 的DeferredResult
了解更多详情。Callable
可以替代WebAsyncTask
这会暴露额外的
超时和完成回调的方法。
异步 Spring MVC 与 WebFlux 的比较
Servlet API 最初是为通过 Filter-Servlet 进行单次传递而构建的
链。异步请求处理允许应用程序退出 Filter-Servlet 链
但保留响应以供进一步处理。Spring MVC 异步支持
是围绕该机制构建的。当控制器返回DeferredResult
这
Filter-Servlet 链被退出,并释放 Servlet 容器线程。后来,当
这DeferredResult
设置时,一个ASYNC
dispatch(到同一 URL),在此期间
controller 再次映射,但不是调用它,DeferredResult
值被使用
(就像控制器返回它一样)以恢复处理。
相比之下,Spring WebFlux 既不是基于 Servlet API 构建的,也不需要这样的 异步请求处理功能,因为它在设计上是异步的。异步 处理内置于所有框架合约中,并由所有 请求处理的阶段。
从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持 异步和响应式类型作为控制器方法中的返回值。 Spring MVC 甚至支持流式处理,包括无功背压。但是,个人 对响应的写入保持阻塞(并在单独的线程上执行),与 WebFlux 不同, 它依赖于非阻塞 I/O,并且每次写入不需要额外的线程。
另一个根本区别是 Spring MVC 不支持异步或响应式
控制器方法参数中的类型(例如@RequestBody
,@RequestPart
等),
它也没有任何显式支持异步和响应式类型作为模型属性。
Spring WebFlux 确实支持所有这些。
最后,从配置的角度来看,必须在 Servlet 容器级别启用异步请求处理功能。
HTTP 流式处理
您可以使用DeferredResult
和Callable
单个异步返回值。
如果您想生成多个异步值并将这些值写入
响应?本节介绍如何执行此作。
对象
您可以使用ResponseBodyEmitter
返回值以生成对象流,其中
每个对象都使用HttpMessageConverter
并写入
response,如以下示例所示:
-
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
) 支持服务器发送的事件,其中从服务器发送的事件
根据 W3C SSE 规范进行格式化。生成 SSE
流式传输,返回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...
}
您可以使用StreamingResponseBody
作为身体在ResponseEntity
自
自定义响应的状态和标头。
反应类型
Spring MVC 支持在控制器中使用响应式客户端库(另请阅读 WebFlux 部分中的响应式库)。
这包括WebClient
从spring-webflux
和其他,例如 Spring Data
响应式数据存储库。在这样的场景下,能够返回很方便
控制器方法中的响应式类型。
响应式返回值的处理方式如下:
-
单值 Promise 适配于,类似于使用
DeferredResult
.例子 包括CompletionStage
(JDK)、Mono'(反应器)和Single
(RxJava)。 -
具有流媒体类型(例如
application/x-ndjson
或text/event-stream
)适应,类似于使用ResponseBodyEmitter
或SseEmitter
.示例包括Flux
(反应堆)或Observable
(RxJava)。 申请也可以返回Flux<ServerSentEvent>
或Observable<ServerSentEvent>
. -
具有任何其他媒体类型(例如
application/json
)被改编 to,类似于使用DeferredResult<List<?>>
.
Spring MVC 通过ReactiveAdapterRegistry 从spring-core ,这允许它适应多个响应式库。 |
对于流式传输到响应,支持响应式背压,但写入
响应仍然阻塞,并通过配置的
AsyncTaskExecutor
,以避免阻塞上游源,例如Flux
返回
从WebClient
.
上下文传播
通常通过以下方式传播上下文java.lang.ThreadLocal
.这可以透明地工作
用于在同一线程上进行处理,但需要额外的工作才能进行异步处理
跨多个线程。Micrometer Context Propagation 库简化了跨线程和跨上下文机制的上下文传播,例如
如ThreadLocal
值
反应堆背景,
GraphQL Java 上下文,
和其他人。
如果类路径上存在 Micrometer Context Propagation,则当控制器方法
返回响应式类型,例如Flux
或Mono
都ThreadLocal
values,其中有一个注册的io.micrometer.ThreadLocalAccessor
,
写入 ReactorContext
作为键值对,使用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()) {
// ...
}
以下内容ThreadLocalAccessor
现成提供实现:
-
LocaleContextThreadLocalAccessor
— 传播LocaleContext
通过LocaleContextHolder
-
RequestAttributesThreadLocalAccessor
— 传播RequestAttributes
通过RequestContextHolder
以上内容不会自动注册。您需要通过以下方式注册它们ContextRegistry.getInstance()
启动时。
断开
当远程客户端离开时,Servlet API 不会提供任何通知。 因此,在流式传输到响应时,无论是通过 SseEmitter 还是响应式类型,定期发送数据都很重要, 因为如果客户端断开连接,写入将失败。发送可以采用以下形式: 空(仅注释)SSE 事件或另一方必须解释的任何其他数据 作为心跳和忽略。
或者,考虑使用 Web 消息传递解决方案(例如 STOMP over WebSocket 或 WebSocket with SockJS) 具有内置心跳机制。
配置
必须在 Servlet 容器级别启用异步请求处理功能。 MVC 配置还公开了异步请求的多个选项。
Servlet 容器
Filter 和 Servlet 声明具有asyncSupported
标志,需要设置为true
以启用异步请求处理。此外,过滤器映射应为
声明以处理ASYNC
jakarta.servlet.DispatchType
.
在 Java 配置中,当您使用AbstractAnnotationConfigDispatcherServletInitializer
初始化 Servlet 容器,这是自动完成的。
在web.xml
配置,您可以添加<async-supported>true</async-supported>
到DispatcherServlet
以及Filter
声明并添加<dispatcher>ASYNC</dispatcher>
以过滤映射。
弹簧 MVC
MVC 配置公开了用于异步请求处理的以下选项:
-
Java 配置:使用
configureAsyncSupport
回调WebMvcConfigurer
. -
XML 命名空间:使用
<async-support>
元素<mvc:annotation-driven>
.
您可以配置以下内容:
-
异步请求的默认超时值取决于 在底层 Servlet 容器上,除非显式设置。
-
AsyncTaskExecutor
用于在使用响应式类型流式传输时阻止写入,以及用于 执行Callable
从控制器方法返回的实例。 默认使用的那个不适合在负载下生产。 -
DeferredResultProcessingInterceptor
implementations 和CallableProcessingInterceptor
实现。
请注意,您还可以在DeferredResult
,
一个ResponseBodyEmitter
和SseEmitter
.对于一个Callable
,您可以使用WebAsyncTask
提供超时值。