对于最新的稳定版本,请使用 Spring Framework 6.2.0spring-doc.cn

异步请求

Spring MVC 与 Servlet 异步请求处理进行了广泛的集成:spring-doc.cn

有关它与 Spring WebFlux 有何不同的概述,请参阅下面的异步 Spring MVC 与 WebFlux 的比较部分。spring-doc.cn

DeferredResult

在 Servlet 容器中启用异步请求处理功能后,控制器方法可以包装任何受支持的控制器方法 返回值,如下例所示:DeferredResultspring-doc.cn

@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 消息)、计划任务或其他事件。spring-doc.cn

Callable

控制器可以用 、 如下例所示:java.util.concurrent.Callablespring-doc.cn

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
	return () -> "someView";
}
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
	// ...
	"someView"
}

然后,可以通过配置的 .TaskExecutorspring-doc.cn

加工

以下是 Servlet 异步请求处理的非常简洁的概述:spring-doc.cn

  • 可以通过调用 将 A 置于异步模式。 这样做的主要效果是 Servlet(以及任何过滤器)可以退出,但 响应将保持打开状态,以便稍后完成处理。ServletRequestrequest.startAsync()spring-doc.cn

  • 对 returns 的调用,您可以将其用于 进一步控制异步处理。例如,它提供了方法 它类似于 Servlet API 的 forward,不同之处在于它允许 应用程序在 Servlet 容器线程上恢复请求处理。request.startAsync()AsyncContextdispatchspring-doc.cn

  • 提供对当前 的访问,您可以 用于区分处理初始请求、异步 dispatch、a forward 和其他 Dispatcher 类型。ServletRequestDispatcherTypespring-doc.cn

DeferredResult处理工作如下:spring-doc.cn

  • 控制器返回 a 并将其保存在内存中的某个 queue 或 list 中。DeferredResultspring-doc.cn

  • Spring MVC 调用 .request.startAsync()spring-doc.cn

  • 同时,和 all configured 过滤器退出请求 processing 线程,但响应保持打开状态。DispatcherServletspring-doc.cn

  • 应用程序从某个线程和 Spring MVC 设置 将请求分派回 Servlet 容器。DeferredResultspring-doc.cn

  • 再次调用 ,并使用 异步生成的返回值。DispatcherServletspring-doc.cn

Callable处理工作如下:spring-doc.cn

  • 控制器返回一个 .Callablespring-doc.cn

  • Spring MVC 调用并将 a 在单独的线程中进行处理。request.startAsync()CallableTaskExecutorspring-doc.cn

  • 同时,和 all 过滤器退出 Servlet 容器线程 但回应仍然开放。DispatcherServletspring-doc.cn

  • 最终 会生成一个结果,Spring MVC 将请求分派回来 添加到 Servlet 容器中以完成处理。Callablespring-doc.cn

  • 再次调用 ,并使用 从 .DispatcherServletCallablespring-doc.cn

有关更多背景和上下文,您还可以阅读 在 Spring MVC 3.2 中引入异步请求处理支持的博客文章。spring-doc.cn

异常处理

使用 时,可以选择是调用 还是使用异常。在这两种情况下,Spring MVC 都会将请求分派回来 添加到 Servlet 容器中以完成处理。然后,它被视为 controller 方法返回给定的值,或者就像它产生了给定的异常一样。 然后,异常通过常规异常处理机制(例如,调用方法)。DeferredResultsetResultsetErrorResult@ExceptionHandlerspring-doc.cn

当您使用 时,会出现类似的处理逻辑,主要区别在于 结果从 返回 或 异常 由它引发。CallableCallablespring-doc.cn

拦截

HandlerInterceptorinstances 可以是 type ,以接收异步启动的初始请求的回调 processing (而不是 and )。AsyncHandlerInterceptorafterConcurrentHandlingStartedpostHandleafterCompletionspring-doc.cn

HandlerInterceptor实现还可以注册 a 或 a ,以便与 异步请求的生命周期(例如,处理超时事件)。有关更多详细信息,请参见 AsyncHandlerInterceptorCallableProcessingInterceptorDeferredResultProcessingInterceptorspring-doc.cn

DeferredResult提供和回调。 有关更多详细信息,请参阅 DeferredResult 的 javadoc。 可以替换为 timeout和completion回调的方法。onTimeout(Runnable)onCompletion(Runnable)CallableWebAsyncTaskspring-doc.cn

异步 Spring MVC 与 WebFlux 的比较

Servlet API 最初是为通过 Filter-Servlet 进行一次传递而构建的 链。异步请求处理允许应用程序退出 Filter-Servlet 链 但将响应保留为打开状态以供进一步处理。Spring MVC 异步支持 是围绕该机制构建的。当控制器返回 时, 退出 Filter-Servlet 链,释放 Servlet 容器线程。稍后,当 ,则进行分派(到同一 URL),在此期间, controller 的 controller 再次映射,但不是调用它,而是使用该值 (就像控制器返回一样)以恢复处理。DeferredResultDeferredResultASYNCDeferredResultspring-doc.cn

相比之下,Spring WebFlux 既不是基于 Servlet API 构建的,也不需要这样的 asynchronous request processing 功能,因为它在设计上是异步的。异步 Handling 内置于所有框架 Contract 中,并且通过 ALL 请求处理阶段。spring-doc.cn

从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持 asynchronous 和 Reactive Types 作为控制器方法中的返回值。 Spring MVC 甚至支持流,包括反应式背压。但是,单个 对响应的写入仍然是阻塞的(并且在单独的线程上执行),这与 WebFlux 不同, 它依赖于非阻塞 I/O,并且每次写入不需要额外的线程。spring-doc.cn

另一个根本区别是 Spring MVC 不支持异步或反应式 控制器方法参数中的类型(例如, , , 等), 它也没有任何明确支持异步和反应类型作为模型属性。 Spring WebFlux 确实支持所有这些。@RequestBody@RequestPartspring-doc.cn

最后,从配置的角度来看,必须在 Servlet 容器级别启用异步请求处理功能。spring-doc.cn

HTTP 流式处理

您可以将 and 用于单个异步返回值。 如果您想生成多个异步值,并将这些值写入 响应?本节介绍如何执行此操作。DeferredResultCallablespring-doc.cn

对象

您可以使用 return 值生成对象流,其中 每个对象都使用 HttpMessageConverter 进行序列化,并写入 响应,如下例所示:ResponseBodyEmitterspring-doc.cn

@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()

你也可以用作 中的 body ,让你 自定义响应的状态和标头。ResponseBodyEmitterResponseEntityspring-doc.cn

当 an 抛出一个 (例如,如果远程客户端消失) 时,应用程序 不负责清理连接,并且不应调用 或 .相反,servlet 容器会自动启动错误通知,其中 Spring MVC 进行调用。 反过来,此调用对应用程序执行一次最终调度,在此期间 Spring MVC 调用配置的异常解析程序并完成请求。emitterIOExceptionemitter.completeemitter.completeWithErrorAsyncListenercompleteWithErrorASYNCspring-doc.cn

上交所

SseEmitter() 的子类 ) 提供对 Server-Sent Events 的支持,其中从服务器发送的事件 根据 W3C SSE 规范进行格式设置。生成 SSE stream 中,返回 ,如下例所示:ResponseBodyEmitterSseEmitterspring-doc.cn

@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)一起使用,该传输将 广泛的浏览器。spring-doc.cn

有关异常处理的说明,另请参阅上一节spring-doc.cn

原始数据

有时,绕过消息转换并直接流式传输到响应(例如,对于文件下载)非常有用。您可以使用返回值类型来执行此操作,如下例所示:OutputStreamStreamingResponseBodyspring-doc.cn

@GetMapping("/download")
public StreamingResponseBody handle() {
	return new StreamingResponseBody() {
		@Override
		public void writeTo(OutputStream outputStream) throws IOException {
			// write...
		}
	};
}
@GetMapping("/download")
fun handle() = StreamingResponseBody {
	// write...
}

您可以用作 to 中的主体 自定义响应的状态和标头。StreamingResponseBodyResponseEntityspring-doc.cn

响应式类型

Spring MVC 支持在控制器中使用反应式 Client 端库(另请阅读 WebFlux 部分中的反应式库)。 这包括 from 和其他 s,例如 Spring Data 反应式数据存储库。在这种情况下,能够返回很方便 来自 controller 方法的 reactive 类型。WebClientspring-webfluxspring-doc.cn

反应式返回值的处理方式如下:spring-doc.cn

  • 单值 promise 适用于,类似于使用 .例子 include (Reactor) 或 (RxJava)。DeferredResultMonoSinglespring-doc.cn

  • 具有流媒体类型(如 或 )的多值流适用于,类似于使用 或 。示例包括 (Reactor) 或 (RxJava)。 应用程序还可以返回 或 。application/x-ndjsontext/event-streamResponseBodyEmitterSseEmitterFluxObservableFlux<ServerSentEvent>Observable<ServerSentEvent>spring-doc.cn

  • 适配具有任何其他媒体类型(如 )的多值流 to 的 e 示例,类似于使用 .application/jsonDeferredResult<List<?>>spring-doc.cn

Spring MVC 通过 ReactiveAdapterRegistry from 支持 Reactor 和 RxJava,这使它能够适应多个反应式库。spring-core

对于流式传输到响应,支持反应式背压,但会写入 响应仍然阻塞,并通过配置的 阻止上游源(例如 return from )。 默认情况下,用于阻塞写入,但这不是 适合在负载下使用。如果您计划使用反应类型进行流式传输,则应使用 MVC 配置来配置任务执行程序。TaskExecutorFluxWebClientSimpleAsyncTaskExecutorspring-doc.cn

上下文传播

通常通过 传播上下文。这是透明的 用于同一线程上的处理,但需要额外的异步处理工作 跨多个线程。Micrometer Context Propagation 库简化了跨线程和跨上下文机制的上下文传播,例如 作为值, 反应器上下文 / GraphQL Java 上下文、 和其他。java.lang.ThreadLocalThreadLocalspring-doc.cn

如果 Micrometer Context Propagation 存在于 Classpath 上,则当控制器方法 返回一个反应类型,例如 or ,所有值,其中有一个已注册的 , 作为键值对写入 Reactor ,使用 由 分配的键。FluxMonoThreadLocalio.micrometer.ThreadLocalAccessorContextThreadLocalAccessorspring-doc.cn

对于其他异步处理场景,您可以使用 Context Propagation 库 径直。例如:spring-doc.cn

Java
// Capture ThreadLocal values from the main thread ...
ContextSnapshot snapshot = ContextSnapshot.captureAll();

// On a different thread: restore ThreadLocal values
try (ContextSnapshot.Scope scope = snapshot.setThreadLocals()) {
	// ...
}

有关更多详细信息,请参阅 Micrometer Context 的文档 传播库。spring-doc.cn

断开

当远程客户端消失时,Servlet API 不提供任何通知。 因此,在流式传输到响应时,无论是通过 SseEmitter 还是反应式类型,定期发送数据都很重要。 因为如果客户端已断开连接,则写入失败。发送可以采用 空(仅评论)SSE 事件或另一方必须解释的任何其他数据 作为心跳并忽略。spring-doc.cn

或者,考虑使用 Web 消息传递解决方案(例如基于 WebSocket 的 STOMP 或带有 SockJS 的 WebSocket) 具有内置心跳机制的 S S S 的 S S S 的 S Tspring-doc.cn

配置

必须在 Servlet 容器级别启用异步请求处理功能。 MVC 配置还为异步请求公开了几个选项。spring-doc.cn

Servlet 容器

Filter 和 Servlet 声明有一个标志,需要设置该标志才能启用异步请求处理。此外,Filter 映射应为 声明处理 .asyncSupportedtrueASYNCjakarta.servlet.DispatchTypespring-doc.cn

在 Java 配置中,当您使用初始化 Servlet 容器时,这是自动完成的。AbstractAnnotationConfigDispatcherServletInitializerspring-doc.cn

在 configuration 中,您可以添加到 和 to 声明以及 add to filter 映射。web.xml<async-supported>true</async-supported>DispatcherServletFilter<dispatcher>ASYNC</dispatcher>spring-doc.cn

Spring MVC

MVC 配置公开了以下与异步请求处理相关的选项:spring-doc.cn

  • Java 配置:使用 on 上的回调。configureAsyncSupportWebMvcConfigurerspring-doc.cn

  • XML 命名空间:使用 下的元素。<async-support><mvc:annotation-driven>spring-doc.cn

您可以配置以下内容:spring-doc.cn

  • 异步请求的默认超时值(如果未设置),则取决于 在底层 Servlet 容器上。spring-doc.cn

  • AsyncTaskExecutor用于在使用 Reactive Types 进行流式处理时阻止写入,以及执行从 controller 方法。如果您满足以下条件,我们强烈建议您配置此属性 stream 或具有返回 的控制器方法,因为 默认情况下,它是一个 .CallableCallableSimpleAsyncTaskExecutorspring-doc.cn

  • DeferredResultProcessingInterceptorimplementations 和 implementations。CallableProcessingInterceptorspring-doc.cn

请注意,您还可以在 、 a 和 .对于 ,可用于提供超时值。DeferredResultResponseBodyEmitterSseEmitterCallableWebAsyncTaskspring-doc.cn