此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

异步请求

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

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

DeferredResult

一旦在 Servlet 容器中启用了异步请求处理功能,控制器方法就可以包装任何受支持的控制器方法 返回值与DeferredResult,如以下示例所示:spring-doc.cadn.net.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.cadn.net.cn

Callable

控制器可以将任何受支持的返回值包装为java.util.concurrent.Callable, 如以下示例所示:spring-doc.cadn.net.cn

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

然后可以通过配置 AsyncTaskExecutor.spring-doc.cadn.net.cn

WebAsyncTask

WebAsyncTask与使用 Callable 相当,但允许自定义其他设置,例如请求超时值和AsyncTaskExecutor执行java.util.concurrent.Callable用代替 为 Spring MVC 全局设置的默认值。下面是使用WebAsyncTask:spring-doc.cadn.net.cn

@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 异步请求处理的非常简明的概述:spring-doc.cadn.net.cn

  • 一个ServletRequest可以通过调用request.startAsync(). 这样做的主要效果是 Servlet(以及任何过滤器)可以退出,但是 响应保持打开状态,以便稍后完成处理。spring-doc.cadn.net.cn

  • 呼吁request.startAsync()返回AsyncContext,您可以将其用于 进一步控制异步处理。例如,它提供了dispatch方法 这类似于 Servlet API 的转发,不同之处在于它允许 应用程序恢复 Servlet 容器线程上的请求处理。spring-doc.cadn.net.cn

  • ServletRequest提供对当前DispatcherType,您可以 用于区分处理初始请求、异步请求 dispatch、forward 和其他调度程序类型。spring-doc.cadn.net.cn

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

  • 控制器返回一个DeferredResult并将其保存在内存中 可以访问它的队列或列表。spring-doc.cadn.net.cn

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

  • 同时,DispatcherServlet并且所有配置的过滤器都会退出请求 processing 线程,但响应保持打开状态。spring-doc.cadn.net.cn

  • 应用程序将DeferredResult来自一些线程,以及 Spring MVC 将请求分派回 Servlet 容器。spring-doc.cadn.net.cn

  • DispatcherServlet再次调用,处理将恢复,并 异步生成的返回值。spring-doc.cadn.net.cn

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

  • 控制器返回一个Callable.spring-doc.cadn.net.cn

  • Spring MVC 调用request.startAsync()并提交Callable自 一AsyncTaskExecutor用于在单独的线程中进行处理。spring-doc.cadn.net.cn

  • 同时,DispatcherServlet并且所有过滤器都退出 Servlet 容器线程, 但回应仍然开放。spring-doc.cadn.net.cn

  • 最终Callable生成结果,Spring MVC 将请求分派回来 到 Servlet 容器以完成处理。spring-doc.cadn.net.cn

  • DispatcherServlet再次调用,处理将恢复,并 异步生成的返回值Callable.spring-doc.cadn.net.cn

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

异常处理

当您使用DeferredResult,可以选择是否调用setResultsetErrorResult但有一个例外。在这两种情况下,Spring MVC 都会将请求分派回来 到 Servlet 容器以完成处理。然后将其视为 controller 方法返回给定值,或者就好像它产生了给定的异常一样。 然后,异常会通过常规异常处理机制(例如,调用@ExceptionHandler方法)。spring-doc.cadn.net.cn

当您使用Callable,出现类似的处理逻辑,主要区别在于 结果从Callable或者它引发异常。spring-doc.cadn.net.cn

拦截

HandlerInterceptor实例可以是AsyncHandlerInterceptor,以接收afterConcurrentHandlingStarted异步启动的初始请求的回调 processing(而不是postHandleafterCompletion).spring-doc.cadn.net.cn

HandlerInterceptor实现还可以注册一个CallableProcessingInterceptorDeferredResultProcessingInterceptor,以更深入地集成 异步请求的生命周期(例如,处理超时事件)。看AsyncHandlerInterceptor了解更多详情。spring-doc.cadn.net.cn

DeferredResult提供onTimeout(Runnable)onCompletion(Runnable)回调。 请参阅javadoc 的DeferredResult了解更多详情。Callable可以替代WebAsyncTask这会暴露额外的 超时和完成回调的方法。spring-doc.cadn.net.cn

异步 Spring MVC 与 WebFlux 的比较

Servlet API 最初是为通过 Filter-Servlet 进行单次传递而构建的 链。异步请求处理允许应用程序退出 Filter-Servlet 链 但保留响应以供进一步处理。Spring MVC 异步支持 是围绕该机制构建的。当控制器返回DeferredResult这 Filter-Servlet 链被退出,并释放 Servlet 容器线程。后来,当 这DeferredResult设置时,一个ASYNCdispatch(到同一 URL),在此期间 controller 再次映射,但不是调用它,DeferredResult值被使用 (就像控制器返回它一样)以恢复处理。spring-doc.cadn.net.cn

相比之下,Spring WebFlux 既不是基于 Servlet API 构建的,也不需要这样的 异步请求处理功能,因为它在设计上是异步的。异步 处理内置于所有框架合约中,并由所有 请求处理的阶段。spring-doc.cadn.net.cn

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

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

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

HTTP 流式处理

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

对象

您可以使用ResponseBodyEmitter返回值以生成对象流,其中 每个对象都使用HttpMessageConverter并写入 response,如以下示例所示:spring-doc.cadn.net.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()

您还可以使用ResponseBodyEmitter作为身体在ResponseEntity,让您 自定义响应的状态和标头。spring-doc.cadn.net.cn

emitter抛出一个IOException(例如,如果远程客户端消失)、应用程序 不负责清理连接,也不应调用emitter.completeemitter.completeWithError.相反,servlet 容器会自动启动AsyncListener错误通知,其中 Spring MVC 将completeWithError叫。 反过来,此调用执行最后一个ASYNCdispatch 到应用程序,在此期间 Spring MVC 调用配置的异常解析器并完成请求。spring-doc.cadn.net.cn

上交所

SseEmitterResponseBodyEmitter) 支持服务器发送的事件,其中从服务器发送的事件 根据 W3C SSE 规范进行格式化。生成 SSE 流式传输,返回SseEmitter,如以下示例所示:spring-doc.cadn.net.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不支持服务器发送的事件。考虑将 Spring 的 WebSocket 消息传递SockJS 后备传输(包括 SSE)一起使用,这些传输针对广泛的浏览器。spring-doc.cadn.net.cn

另请参阅上一节,了解有关异常处理的注释。spring-doc.cadn.net.cn

原始数据

有时,绕过消息转换并直接流式传输到响应很有用OutputStream(例如,用于文件下载)。您可以使用StreamingResponseBody返回值类型,如以下示例所示:spring-doc.cadn.net.cn

@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-doc.cadn.net.cn

反应类型

Spring MVC 支持在控制器中使用响应式客户端库(另请阅读 WebFlux 部分中的响应式库)。 这包括WebClientspring-webflux和其他,例如 Spring Data 响应式数据存储库。在这样的场景下,能够返回很方便 控制器方法中的响应式类型。spring-doc.cadn.net.cn

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

  • 单值 Promise 适配于,类似于使用DeferredResult.例子 包括CompletionStage(JDK)、Mono'(反应器)和Single(RxJava)。spring-doc.cadn.net.cn

  • 具有流媒体类型(例如application/x-ndjsontext/event-stream)适应,类似于使用ResponseBodyEmitterSseEmitter.示例包括Flux(反应堆)或Observable(RxJava)。 申请也可以返回Flux<ServerSentEvent>Observable<ServerSentEvent>.spring-doc.cadn.net.cn

  • 具有任何其他媒体类型(例如application/json)被改编 to,类似于使用DeferredResult<List<?>>.spring-doc.cadn.net.cn

Spring MVC 通过ReactiveAdapterRegistryspring-core,这允许它适应多个响应式库。

对于流式传输到响应,支持响应式背压,但写入 响应仍然阻塞,并通过配置 AsyncTaskExecutor,以避免阻塞上游源,例如Flux返回 从WebClient.spring-doc.cadn.net.cn

上下文传播

通常通过以下方式传播上下文java.lang.ThreadLocal.这可以透明地工作 用于在同一线程上进行处理,但需要额外的工作才能进行异步处理 跨多个线程。Micrometer Context Propagation 库简化了跨线程和跨上下文机制的上下文传播,例如 如ThreadLocal值 反应堆背景, GraphQL Java 上下文, 和其他人。spring-doc.cadn.net.cn

如果类路径上存在 Micrometer Context Propagation,则当控制器方法 返回响应式类型,例如FluxMonoThreadLocalvalues,其中有一个注册的io.micrometer.ThreadLocalAccessor, 写入 ReactorContext作为键值对,使用ThreadLocalAccessor.spring-doc.cadn.net.cn

对于其他异步处理方案,可以使用上下文传播库 径直。例如:spring-doc.cadn.net.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()) {
	// ...
}

以下内容ThreadLocalAccessor现成提供实现:spring-doc.cadn.net.cn

以上内容不会自动注册。您需要通过以下方式注册它们ContextRegistry.getInstance()启动时。spring-doc.cadn.net.cn

有关更多详细信息,请参阅 千分尺上下文传播库。spring-doc.cadn.net.cn

断开

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

或者,考虑使用 Web 消息传递解决方案(例如 STOMP over WebSocketWebSocket with SockJS) 具有内置心跳机制。spring-doc.cadn.net.cn

配置

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

Servlet 容器

Filter 和 Servlet 声明具有asyncSupported标志,需要设置为true以启用异步请求处理。此外,过滤器映射应为 声明以处理ASYNC jakarta.servlet.DispatchType.spring-doc.cadn.net.cn

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

web.xml配置,您可以添加<async-supported>true</async-supported>DispatcherServlet以及Filter声明并添加<dispatcher>ASYNC</dispatcher>以过滤映射。spring-doc.cadn.net.cn

弹簧 MVC

MVC 配置公开了用于异步请求处理的以下选项:spring-doc.cadn.net.cn

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

  • 异步请求的默认超时值取决于 在底层 Servlet 容器上,除非显式设置。spring-doc.cadn.net.cn

  • AsyncTaskExecutor用于在使用响应式类型流式传输时阻止写入,以及用于 执行Callable从控制器方法返回的实例。 默认使用的那个不适合在负载下生产。spring-doc.cadn.net.cn

  • DeferredResultProcessingInterceptorimplementations 和CallableProcessingInterceptor实现。spring-doc.cadn.net.cn

请注意,您还可以在DeferredResult, 一个ResponseBodyEmitterSseEmitter.对于一个Callable,您可以使用WebAsyncTask提供超时值。spring-doc.cadn.net.cn