响应式 Web 应用程序

Spring Boot 通过为 Spring Webflux 提供自动配置,简化了响应式 Web 应用程序的开发。

“Spring WebFlux Framework”

Spring WebFlux 是 Spring Framework 5.0 中引入的新响应式 Web 框架。 与 Spring MVC 不同,它不需要 servlet API,完全异步和非阻塞,并通过 Reactor 项目 实现了 Reactive Streams 规范。

Spring WebFlux 有两种风格:函数式和基于注解的。 基于注解的方式与 Spring MVC 模型非常接近,如下例所示:

  • Java

  • Kotlin

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class MyRestController {

	private final UserRepository userRepository;

	private final CustomerRepository customerRepository;

	public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
		this.userRepository = userRepository;
		this.customerRepository = customerRepository;
	}

	@GetMapping("/{userId}")
	public Mono<User> getUser(@PathVariable Long userId) {
		return this.userRepository.findById(userId);
	}

	@GetMapping("/{userId}/customers")
	public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
		return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
	}

	@DeleteMapping("/{userId}")
	public Mono<Void> deleteUser(@PathVariable Long userId) {
		return this.userRepository.deleteById(userId);
	}

}
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {

	@GetMapping("/{userId}")
	fun getUser(@PathVariable userId: Long): Mono<User?> {
		return userRepository.findById(userId)
	}

	@GetMapping("/{userId}/customers")
	fun getUserCustomers(@PathVariable userId: Long): Flux<Customer> {
		return userRepository.findById(userId).flatMapMany { user: User? ->
			customerRepository.findByUser(user)
		}
	}

	@DeleteMapping("/{userId}")
	fun deleteUser(@PathVariable userId: Long): Mono<Void> {
		return userRepository.deleteById(userId)
	}

}

WebFlux 是 Spring Framework 的一部分,详细信息可在其 参考文档 中找到。

“WebFlux.fn”,即函数式变体,将路由配置与实际请求处理分开,如下例所示:

  • Java

  • Kotlin

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

	private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

	@Bean
	public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
		return route()
				.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
				.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
				.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
				.build();
	}

}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RequestPredicates.DELETE
import org.springframework.web.reactive.function.server.RequestPredicates.GET
import org.springframework.web.reactive.function.server.RequestPredicates.accept
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerResponse

@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {

	@Bean
	fun monoRouterFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
		return RouterFunctions.route(
			GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute(
			GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute(
			DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser)
	}

	companion object {
		private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
	}

}
  • Java

  • Kotlin

import reactor.core.publisher.Mono;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

@Component
public class MyUserHandler {

	public Mono<ServerResponse> getUser(ServerRequest request) {
		...
	}

	public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
		...
	}

	public Mono<ServerResponse> deleteUser(ServerRequest request) {
		...
	}

}
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class MyUserHandler {

	fun getUser(request: ServerRequest?): Mono<ServerResponse> {
		...
	}

	fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
		...
	}

	fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
		...
	}

}

“WebFlux.fn” 是 Spring Framework 的一部分,详细信息可在其 参考文档 中找到。

您可以定义任意数量的 RouterFunction beans 来模块化路由定义。 如果需要应用优先级,可以对 beans 进行排序。

要开始使用,请将 spring-boot-starter-webflux 模块添加到您的应用程序中。

在您的应用程序中同时添加 spring-boot-starter-webspring-boot-starter-webflux 模块会导致 Spring Boot 自动配置 Spring MVC,而不是 WebFlux。 选择这种行为是因为许多 Spring 开发人员将 spring-boot-starter-webflux 添加到他们的 Spring MVC 应用程序中以使用响应式 WebClient。 您仍然可以通过设置 SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE) 来强制执行您的选择。

Spring WebFlux 自动配置

Spring Boot 为 Spring WebFlux 提供了自动配置,适用于大多数应用程序。

自动配置在 Spring 默认值的基础上添加了以下功能:

  • 配置 HttpMessageReaderHttpMessageWriter 实例的编解码器(在本文档后面描述)。

  • 支持提供静态资源,包括 WebJars 支持(在本文档后面描述)。

如果您想保留 Spring Boot WebFlux 功能并添加额外的 WebFlux 配置,您可以添加自己的 @Configuration 类,类型为 WebFluxConfigurer,但*不*使用 @EnableWebFlux

如果您想对自动配置的 HttpHandler 进行额外的自定义,您可以定义类型为 WebHttpHandlerBuilderCustomizer 的 beans,并使用它们来修改 WebHttpHandlerBuilder

如果您想完全控制 Spring WebFlux,您可以添加自己的 @Configuration 类,并使用 @EnableWebFlux 进行注解。

Spring WebFlux 转换服务

如果您想自定义 Spring WebFlux 使用的 ConversionService,您可以提供一个带有 addFormatters 方法的 WebFluxConfigurer bean。

也可以使用 spring.webflux.format.* 配置属性来自定义转换。 未配置时,使用以下默认值:

属性 DateTimeFormatter 格式

spring.webflux.format.date

ofLocalizedDate(FormatStyle.SHORT)

java.util.DateLocalDate

spring.webflux.format.time

ofLocalizedTime(FormatStyle.SHORT)

java.time 的 LocalTimeOffsetTime

spring.webflux.format.date-time

ofLocalizedDateTime(FormatStyle.SHORT)

java.time 的 LocalDateTimeOffsetDateTimeZonedDateTime

使用 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器

Spring WebFlux 使用 HttpMessageReaderHttpMessageWriter 接口来转换 HTTP 请求和响应。 它们通过 CodecConfigurer 进行配置,通过查看类路径中可用的库来设置合理的默认值。

Spring Boot 为编解码器提供了专门的配置属性 spring.http.codecs.*。 它还通过使用 CodecCustomizer 实例来应用进一步的自定义。 例如,spring.jackson.* 配置键应用于 Jackson 编解码器。

如果需要添加或自定义编解码器,可以创建自定义的 CodecCustomizer 组件,如下例所示:

  • Java

  • Kotlin

import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;

@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {

	@Bean
	public CodecCustomizer myCodecCustomizer() {
		return (configurer) -> {
			configurer.registerDefaults(false);
			configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
			// ...
		};
	}

}
import org.springframework.boot.web.codec.CodecCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.http.codec.CodecConfigurer
import org.springframework.http.codec.ServerSentEventHttpMessageReader

class MyCodecsConfiguration {

	@Bean
	fun myCodecCustomizer(): CodecCustomizer {
		return CodecCustomizer { configurer: CodecConfigurer ->
			configurer.registerDefaults(false)
			configurer.customCodecs().register(ServerSentEventHttpMessageReader())
		}
	}

}

静态内容

默认情况下,Spring Boot 从类路径中名为 /static(或 /public/resources/META-INF/resources)的目录提供静态内容。 它使用来自 Spring WebFlux 的 ResourceWebHandler,因此您可以通过添加自己的 WebFluxConfigurer 并覆盖 addResourceHandlers 方法来修改该行为。

默认情况下,资源映射到 /**,但您可以通过设置 spring.webflux.static-path-pattern 属性来调整。 例如,可以通过以下方式将所有资源重新定位到 /resources/**

  • Properties

  • YAML

spring.webflux.static-path-pattern=/resources/**
spring:
  webflux:
    static-path-pattern: "/resources/**"

您还可以使用 spring.web.resources.static-locations 来自定义静态资源位置。 这样做会用目录位置列表替换默认值。 如果这样做,默认欢迎页面检测将切换到您的自定义位置。 因此,如果在启动时您的任何位置中有 index.html,它就是应用程序的主页。

除了前面列出的"标准"静态资源位置外,还对 Webjars 内容 进行了特殊处理。 默认情况下,如果资源以 Webjars 格式打包,则从 jar 文件中提供路径为 /webjars/** 的任何资源。 可以使用 spring.webflux.webjars-path-pattern 属性自定义路径。

Spring WebFlux 应用程序不严格依赖 servlet API,因此它们不能作为 war 文件部署,也不使用 src/main/webapp 目录。

欢迎页面

Spring Boot 支持静态和模板化的欢迎页面。 它首先在配置的静态内容位置中查找 index.html 文件。 如果未找到,则查找 index 模板。 如果找到其中任何一个,它将自动用作应用程序的欢迎页面。

这仅作为应用程序定义的实际索引路由的后备。 顺序由 HandlerMapping beans 的顺序定义,默认顺序如下:

org.springframework.web.reactive.function.server.support.RouterFunctionMapping

使用 RouterFunction beans 声明的端点

org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping

@Controller beans 中声明的端点

RouterFunctionMapping for the Welcome Page

欢迎页面支持

模板引擎

除了 REST Web 服务外,您还可以使用 Spring WebFlux 来提供动态 HTML 内容。 Spring WebFlux 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 Mustache。

Spring Boot 包含以下模板引擎的自动配置支持:

并非所有 FreeMarker 功能都支持 WebFlux。 有关更多详细信息,请查看每个属性的描述。

当您使用这些模板引擎之一并采用默认配置时,您的模板会自动从 src/main/resources/templates 中获取。

错误处理

Spring Boot 提供了一个 WebExceptionHandler,它以合理的方式处理所有错误。 它在处理顺序中的位置紧接在 WebFlux 提供的处理程序之前,这些处理程序被视为最后。 对于机器客户端,它生成一个包含错误详情、HTTP 状态和异常消息的 JSON 响应。 对于浏览器客户端,有一个"白标"错误处理程序,它以 HTML 格式呈现相同的数据。 您还可以提供自己的 HTML 模板来显示错误(请参阅 下一节)。

在直接在 Spring Boot 中自定义错误处理之前,您可以利用 Spring WebFlux 中的 RFC 9457 Problem Details 支持。 Spring WebFlux 可以生成带有 application/problem+json 媒体类型的自定义错误消息,例如:

{
	"type": "https://example.org/problems/unknown-project",
	"title": "Unknown project",
	"status": 404,
	"detail": "No project found for id 'spring-unknown'",
	"instance": "/projects/spring-unknown"
}

可以通过将 spring.webflux.problemdetails.enabled 设置为 true 来启用此支持。

自定义此功能的第一步通常涉及使用现有机制但替换或增强错误内容。 为此,您可以添加一个类型为 ErrorAttributes 的 bean。

要更改错误处理行为,您可以实现 ErrorWebExceptionHandler 并注册该类型的 bean 定义。 因为 ErrorWebExceptionHandler 相当底层,Spring Boot 还提供了一个方便的 AbstractErrorWebExceptionHandler,让您以 WebFlux 函数式方式处理错误,如下例所示:

  • Java

  • Kotlin

import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder;

@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

	public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties,
			ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) {
		super(errorAttributes, webProperties.getResources(), applicationContext);
		setMessageReaders(serverCodecConfigurer.getReaders());
		setMessageWriters(serverCodecConfigurer.getWriters());
	}

	@Override
	protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
		return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
	}

	private boolean acceptsXml(ServerRequest request) {
		return request.headers().accept().contains(MediaType.APPLICATION_XML);
	}

	public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
		BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
		// ... additional builder calls
		return builder.build();
	}

}
import org.springframework.boot.autoconfigure.web.WebProperties
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.codec.ServerCodecConfigurer
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class MyErrorWebExceptionHandler(
		errorAttributes: ErrorAttributes, webProperties: WebProperties,
		applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer
) : AbstractErrorWebExceptionHandler(errorAttributes, webProperties.resources, applicationContext) {

	init {
		setMessageReaders(serverCodecConfigurer.readers)
		setMessageWriters(serverCodecConfigurer.writers)
	}

	override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
		return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
	}

	private fun acceptsXml(request: ServerRequest): Boolean {
		return request.headers().accept().contains(MediaType.APPLICATION_XML)
	}

	fun handleErrorAsXml(request: ServerRequest): Mono<ServerResponse> {
		val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
		// ... additional builder calls
		return builder.build()
	}

}

为了更完整的了解,您还可以直接子类化 DefaultErrorWebExceptionHandler 并覆盖特定方法。

在某些情况下,控制器级别处理的错误不会被 Web 观察或 指标基础设施记录。 应用程序可以通过 在观察上下文中设置已处理的异常来确保此类异常被记录。

自定义错误页面

如果您想为给定的状态码显示自定义 HTML 错误页面,您可以添加从 error/* 解析的视图,例如通过将文件添加到 /error 目录。 错误页面可以是静态 HTML(即添加在任何静态资源目录下)或使用模板构建。 文件名应该是确切的状态码、状态码系列掩码,或者如果没有其他匹配项,则为 error。 请注意,默认错误视图的路径是 error/error,而 Spring MVC 的默认错误视图是 error

例如,要将 404 映射到静态 HTML 文件,您的目录结构如下:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>

要使用 Mustache 模板映射所有 5xx 错误,您的目录结构如下:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.mustache
             +- <other templates>

Web 过滤器

Spring WebFlux 提供了一个 WebFilter 接口,可以实现该接口来过滤 HTTP 请求-响应交换。 在应用程序上下文中找到的 WebFilter beans 将自动用于过滤每个交换。

当过滤器的顺序很重要时,它们可以实现 Ordered 或使用 @Order 进行注解。 Spring Boot 自动配置可能会为您配置 Web 过滤器。 当它这样做时,将使用下表中显示的顺序:

Web 过滤器 顺序

WebFilterChainProxy (Spring Security)

-100

HttpExchangesWebFilter

Ordered.LOWEST_PRECEDENCE - 10

嵌入式响应式服务器支持

Spring Boot 包括对以下嵌入式响应式 Web 服务器的支持:Reactor Netty、Tomcat、Jetty 和 Undertow。 大多数开发人员使用适当的 starter 来获取完全配置的实例。 默认情况下,嵌入式服务器在端口 8080 上监听 HTTP 请求。

自定义响应式服务器

可以通过使用 Spring Environment 属性来配置常见的响应式 Web 服务器设置。 通常,您会在 application.propertiesapplication.yaml 文件中定义这些属性。

常见的服务器设置包括:

  • 网络设置:传入 HTTP 请求的监听端口(server.port)、要绑定的接口地址(server.address)等。

  • 错误管理:错误页面的位置(server.error.path)等。

  • SSL

  • HTTP 压缩

Spring Boot 尽可能多地暴露常见设置,但这并不总是可能的。 对于这些情况,server.netty.* 等专用命名空间提供了服务器特定的自定义。

有关完整列表,请参阅 ServerProperties 类。

编程式自定义

如果需要以编程方式配置响应式 Web 服务器,可以注册一个实现 WebServerFactoryCustomizer 接口的 Spring bean。 WebServerFactoryCustomizer 提供对 ConfigurableReactiveWebServerFactory 的访问,其中包含许多自定义 setter 方法。 以下示例显示了以编程方式设置端口:

  • Java

  • Kotlin

import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {

	@Override
	public void customize(ConfigurableReactiveWebServerFactory server) {
		server.setPort(9000);
	}

}
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory
import org.springframework.stereotype.Component

@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {

	override fun customize(server: ConfigurableReactiveWebServerFactory) {
		server.setPort(9000)
	}

}

JettyReactiveWebServerFactoryNettyReactiveWebServerFactoryTomcatReactiveWebServerFactoryUndertowReactiveWebServerFactoryConfigurableReactiveWebServerFactory 的专用变体,它们分别为 Jetty、Reactor Netty、Tomcat 和 Undertow 提供了额外的自定义 setter 方法。 以下示例显示了如何自定义 NettyReactiveWebServerFactory,它提供对 Reactor Netty 特定配置选项的访问:

  • Java

  • Kotlin

import java.time.Duration;

import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyNettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {

	@Override
	public void customize(NettyReactiveWebServerFactory factory) {
		factory.addServerCustomizers((server) -> server.idleTimeout(Duration.ofSeconds(20)));
	}

}
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
import java.time.Duration

@Component
class MyNettyWebServerFactoryCustomizer : WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {

	override fun customize(factory: NettyReactiveWebServerFactory) {
		factory.addServerCustomizers({ server -> server.idleTimeout(Duration.ofSeconds(20)) })
	}

}

直接自定义 ConfigurableReactiveWebServerFactory

对于需要从 ReactiveWebServerFactory 扩展的更高级用例,您可以自己暴露该类型的 bean。

为许多配置选项提供了 setter。 如果您需要做一些更奇特的事情,还提供了几个受保护的"钩子"方法。 有关详细信息,请参阅 ConfigurableReactiveWebServerFactory API 文档。

自动配置的自定义器仍然应用于您的自定义工厂,因此请谨慎使用该选项。

响应式服务器资源配置

在自动配置 Reactor Netty 或 Jetty 服务器时,Spring Boot 将创建特定的 beans,这些 beans 将为服务器实例提供 HTTP 资源:ReactorResourceFactoryJettyResourceFactory

默认情况下,这些资源也将与 Reactor Netty 和 Jetty 客户端共享,以实现最佳性能,前提是:

  • 服务器和客户端使用相同的技术

  • 客户端实例是使用 Spring Boot 自动配置的 WebClient.Builder bean 构建的

开发人员可以通过提供自定义的 ReactorResourceFactoryJettyResourceFactory bean 来覆盖 Jetty 和 Reactor Netty 的资源配置 - 这将同时应用于客户端和服务器。

您可以在 WebClient 运行时 部分了解有关客户端资源配置的更多信息。