此版本仍在开发中,尚未被视为稳定版。如需最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

响应式核心

spring-web 模块包含以下对响应式 Web 应用程序的基础支持:spring-doc.cadn.net.cn

HttpHandler

HttpHandler 是一个简单的契约,仅包含一个用于处理请求和响应的方法。它被有意设计得极为精简,其主要且唯一的目的就是对不同的 HTTP 服务器 API 提供一个最小化的抽象。spring-doc.cadn.net.cn

下表描述了所支持的服务器 API:spring-doc.cadn.net.cn

服务器名称 所使用的服务器 API 响应式流支持

Nettyspring-doc.cadn.net.cn

Netty APIspring-doc.cadn.net.cn

Reactor Nettyspring-doc.cadn.net.cn

Undertowspring-doc.cadn.net.cn

Undertow APIspring-doc.cadn.net.cn

spring-web:Undertow 到响应式流(Reactive Streams)的桥接spring-doc.cadn.net.cn

Tomcatspring-doc.cadn.net.cn

Servlet 非阻塞 I/O;Tomcat API 用于读写 ByteBuffer 与 byte[]spring-doc.cadn.net.cn

spring-web:Servlet 非阻塞 I/O 到响应式流(Reactive Streams)的桥接spring-doc.cadn.net.cn

Jettyspring-doc.cadn.net.cn

Servlet 非阻塞 I/O;Jetty API 写入 ByteBuffer 与 byte[] 的对比spring-doc.cadn.net.cn

spring-web:Servlet 非阻塞 I/O 到响应式流(Reactive Streams)的桥接spring-doc.cadn.net.cn

Servlet 容器spring-doc.cadn.net.cn

Servlet 非阻塞 I/Ospring-doc.cadn.net.cn

spring-web:Servlet 非阻塞 I/O 到响应式流(Reactive Streams)的桥接spring-doc.cadn.net.cn

下表描述了服务器依赖项(另请参阅支持的版本):spring-doc.cadn.net.cn

服务器名称 组 ID 构件名称

Reactor Nettyspring-doc.cadn.net.cn

io.projectreactor.nettyspring-doc.cadn.net.cn

reactor-nettyspring-doc.cadn.net.cn

Undertowspring-doc.cadn.net.cn

io.undertowspring-doc.cadn.net.cn

undertow-corespring-doc.cadn.net.cn

Tomcatspring-doc.cadn.net.cn

org.apache.tomcat.embedspring-doc.cadn.net.cn

tomcat-embed-corespring-doc.cadn.net.cn

Jettyspring-doc.cadn.net.cn

org.eclipse.jettyspring-doc.cadn.net.cn

jetty-server、jetty-servletspring-doc.cadn.net.cn

下面的代码片段展示了如何将 HttpHandler 适配器与各个服务器 API 一起使用:spring-doc.cadn.net.cn

Reactor Nettyspring-doc.cadn.net.cn

HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bindNow();
val handler: HttpHandler = ...
val adapter = ReactorHttpHandlerAdapter(handler)
HttpServer.create().host(host).port(port).handle(adapter).bindNow()
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
val handler: HttpHandler = ...
val adapter = UndertowHttpHandlerAdapter(handler)
val server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build()
server.start()
HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);

Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();
val handler: HttpHandler = ...
val servlet = TomcatHttpHandlerAdapter(handler)

val server = Tomcat()
val base = File(System.getProperty("java.io.tmpdir"))
val rootContext = server.addContext("", base.absolutePath)
Tomcat.addServlet(rootContext, "main", servlet)
rootContext.addServletMappingDecoded("/", "main")
server.host = host
server.setPort(port)
server.start()
HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);

Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();

ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();
val handler: HttpHandler = ...
val servlet = JettyHttpHandlerAdapter(handler)

val server = Server()
val contextHandler = ServletContextHandler(server, "")
contextHandler.addServlet(ServletHolder(servlet), "/")
contextHandler.start();

val connector = ServerConnector(server)
connector.host = host
connector.port = port
server.addConnector(connector)
server.start()

Servlet 容器spring-doc.cadn.net.cn

若要作为 WAR 部署到任何 Servlet 容器,您可以扩展并包含 AbstractReactiveWebInitializer 到 WAR 中。该类使用 ServletHttpHandlerAdapter 包装一个 HttpHandler, 并将其注册为 Servletspring-doc.cadn.net.cn

WebHandlerAPI

org.springframework.web.server 包基于 HttpHandler 契约构建,提供了一个通用的 Web API,用于通过由多个 WebExceptionHandler、多个 WebFilter 以及单个 WebHandler 组件组成的链来处理请求。该链可以通过 WebHttpHandlerBuilder 组装,只需指向一个 Spring ApplicationContext(其中的组件会被 自动检测),和/或通过向构建器注册组件来完成。spring-doc.cadn.net.cn

虽然 HttpHandler 的目标很简单,即抽象化不同 HTTP 服务器的使用,但 WebHandler API 旨在提供更广泛的功能集,这些功能通常用于 Web 应用程序中,例如:spring-doc.cadn.net.cn

特殊 Bean 类型

下表列出了 WebHttpHandlerBuilder 可以在 Spring ApplicationContext 中自动检测到的组件,或者可以直接向其注册的组件:spring-doc.cadn.net.cn

Bean 名称 Bean 类型 计数 描述

<any>spring-doc.cadn.net.cn

WebExceptionHandlerspring-doc.cadn.net.cn

0..Nspring-doc.cadn.net.cn

为来自 WebFilter 实例链和目标 WebHandler 的异常提供处理。更多详情,请参阅异常spring-doc.cadn.net.cn

<any>spring-doc.cadn.net.cn

WebFilterspring-doc.cadn.net.cn

0..Nspring-doc.cadn.net.cn

将拦截风格的逻辑应用于过滤器链其余部分以及目标 WebHandler 的前后。更多详情,请参见过滤器spring-doc.cadn.net.cn

webHandlerspring-doc.cadn.net.cn

WebHandlerspring-doc.cadn.net.cn

1spring-doc.cadn.net.cn

请求的处理器。spring-doc.cadn.net.cn

webSessionManagerspring-doc.cadn.net.cn

WebSessionManagerspring-doc.cadn.net.cn

0..1spring-doc.cadn.net.cn

通过 WebSession 上的一个方法暴露的 ServerWebExchange 实例的管理器。 默认为 DefaultWebSessionManagerspring-doc.cadn.net.cn

serverCodecConfigurerspring-doc.cadn.net.cn

ServerCodecConfigurerspring-doc.cadn.net.cn

0..1spring-doc.cadn.net.cn

用于访问 HttpMessageReader 实例,以解析表单数据和多部分(multipart)数据,这些数据随后通过 ServerWebExchange 上的方法暴露出来。默认情况下由 ServerCodecConfigurer.create() 提供。spring-doc.cadn.net.cn

localeContextResolverspring-doc.cadn.net.cn

LocaleContextResolverspring-doc.cadn.net.cn

0..1spring-doc.cadn.net.cn

通过 LocaleContext 上的一个方法暴露的 ServerWebExchange 解析器。 默认为 AcceptHeaderLocaleContextResolverspring-doc.cadn.net.cn

forwardedHeaderTransformerspring-doc.cadn.net.cn

ForwardedHeaderTransformerspring-doc.cadn.net.cn

0..1spring-doc.cadn.net.cn

用于处理转发类型(forwarded type)的头部信息,可以提取并移除这些头部,也可以仅移除它们。 默认情况下不使用。spring-doc.cadn.net.cn

表单数据

ServerWebExchange 提供了以下方法用于访问表单数据:spring-doc.cadn.net.cn

Mono<MultiValueMap<String, String>> getFormData();
suspend fun getFormData(): MultiValueMap<String, String>

DefaultServerWebExchange 使用配置好的 HttpMessageReader 将表单数据(application/x-www-form-urlencoded)解析为一个 MultiValueMap。默认情况下,FormHttpMessageReaderServerCodecConfigurer bean 进行配置(参见Web Handler API)。spring-doc.cadn.net.cn

多部分数据

ServerWebExchange 提供了以下方法用于访问多部分(multipart)数据:spring-doc.cadn.net.cn

Mono<MultiValueMap<String, Part>> getMultipartData();
suspend fun getMultipartData(): MultiValueMap<String, Part>

DefaultServerWebExchange 使用配置的 HttpMessageReader<MultiValueMap<String, Part>>multipart/form-datamultipart/mixedmultipart/related 内容解析为 MultiValueMap。 默认情况下,这是 DefaultPartHttpMessageReader,它没有任何第三方依赖项。 或者,也可以使用基于 Synchronoss NIO Multipart 库的 SynchronossPartHttpMessageReader。 两者均通过 ServerCodecConfigurer Bean 进行配置(请参阅 Web Handler API)。spring-doc.cadn.net.cn

要以流式方式解析 multipart 数据,您可以使用 Flux<PartEvent> 返回的 PartEventHttpMessageReader,而不是使用 @RequestPart。因为 Map 意味着通过名称对各个部分进行类似 @RequestBody 的访问,因此需要完整地解析整个 multipart 数据。 相比之下,您可以使用 Flux<PartEvent> 将内容解码为 MultiValueMap,而无需将其收集到 7 中。spring-doc.cadn.net.cn

转发的请求头

当请求经过负载均衡器等代理时,主机、端口和协议方案可能会发生变化,这使得从客户端视角创建指向正确主机、端口和协议方案的链接变得具有挑战性。spring-doc.cadn.net.cn

RFC 7239 定义了 Forwarded HTTP 请求头, 代理可以使用该请求头来提供有关原始请求的信息。spring-doc.cadn.net.cn

非标准头信息

还有其他一些非标准的头部字段,包括 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefixspring-doc.cadn.net.cn

X-Forwarded-Host

虽然并非标准,但 X-Forwarded-Host: <host> 是一个事实上的标准请求头,用于将原始主机信息传达给下游服务器。例如,如果将针对 example.com/resource 的请求发送到代理,而该代理将请求转发到 localhost:8080/resource,则可以发送一个 X-Forwarded-Host: example.com 请求头,以告知服务器原始主机为 example.comspring-doc.cadn.net.cn

X-Forwarded-Port

虽然不是标准规范,但 X-Forwarded-Port: <port> 是一种事实上的标准请求头,用于将原始端口信息传递给下游服务器。例如,如果一个对 example.com/resource 的请求被发送到某个代理服务器,该代理服务器再将请求转发至 localhost:8080/resource,那么可以附带一个 X-Forwarded-Port: 443 请求头,以告知目标服务器原始请求的端口是 443spring-doc.cadn.net.cn

X-Forwarded-Proto

虽然这不是标准,但 X-Forwarded-Proto: (https|http) 是一个事实上的标准头部,用于向下游服务器传达原始协议(例如,https / http)。例如,如果将 example.com/resource 的请求发送到 代理,该代理将请求转发到 localhost:8080/resource,则可以发送一个 X-Forwarded-Proto: https 的头部,以通知服务器原始协议是 httpsspring-doc.cadn.net.cn

X-Forwarded-Ssl

虽然不是标准规范,但 X-Forwarded-Ssl: (on|off) 是一个事实上的标准请求头,用于向下游服务器传递原始协议(例如 http / https)。例如,如果对 example.com/resource 的请求被发送到一个代理服务器,该代理将请求转发至 localhost:8080/resource,那么代理会添加 X-Forwarded-Ssl: on 请求头,以告知后端服务器原始协议为 httpsspring-doc.cadn.net.cn

X-Forwarded-Prefix

虽然并非标准,但 X-Forwarded-Prefix: <prefix> 是一个事实上的标准请求头,用于将原始 URL 路径前缀传达给下游服务器。spring-doc.cadn.net.cn

X-Forwarded-Prefix 的使用会因部署场景而异,需要具备灵活性,以便替换、移除或在目标服务器的路径前缀前添加内容。spring-doc.cadn.net.cn

场景1:覆盖路径前缀spring-doc.cadn.net.cn

https://example.com/api/{path} -> http://localhost:8080/app1/{path}

前缀是捕获组 {path} 之前路径的起始部分。对于代理来说,前缀是 /api,而对于服务器来说,前缀是 /app1。在这种情况下,代理可以发送 X-Forwarded-Prefix: /api,以使原始前缀 /api 覆盖服务器前缀 /app1spring-doc.cadn.net.cn

场景2:移除路径前缀spring-doc.cadn.net.cn

有时,应用程序可能希望移除该前缀。例如,考虑以下代理到服务器的映射:spring-doc.cadn.net.cn

https://app1.example.com/{path} -> http://localhost:8080/app1/{path}
https://app2.example.com/{path} -> http://localhost:8080/app2/{path}

代理没有前缀,而应用程序 app1app2 分别具有路径前缀 /app1/app2。代理可以发送 X-Forwarded-Prefix: , 以使用空前缀覆盖服务器上的前缀 /app1/app2spring-doc.cadn.net.cn

这种部署场景的一个常见情况是,许可证按生产应用服务器收费,因此更倾向于在每台服务器上部署多个应用程序以降低费用。另一个原因是在同一台服务器上运行更多应用程序,以便共享服务器运行所需的资源。spring-doc.cadn.net.cn

在这些场景中,应用程序需要一个非空的上下文根路径,因为同一服务器上部署了多个应用程序。然而,在公共 API 的 URL 路径中不应显示该上下文根路径,因为应用程序可能会使用不同的子域名,这样可以带来如下好处:spring-doc.cadn.net.cn

场景3:插入路径前缀spring-doc.cadn.net.cn

在其他情况下,可能需要添加前缀。例如,考虑以下代理到服务器的映射:spring-doc.cadn.net.cn

https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}

在这种情况下,代理的前缀为 /api/app1,而服务器的前缀为 /app1。代理可以发送 X-Forwarded-Prefix: /api/app1,以使原始前缀 /api/app1 覆盖服务器前缀 /app1spring-doc.cadn.net.cn

ForwardedHeaderTransformer

ForwardedHeaderTransformer 是一个组件,它根据转发头(forwarded headers)修改请求的主机、端口和协议(scheme),然后移除这些头信息。如果你将其声明为名为 forwardedHeaderTransformer 的 Bean,它将被自动检测并使用。spring-doc.cadn.net.cn

在 5.1 版本中,ForwardedHeaderFilter 已被弃用,并由 ForwardedHeaderTransformer 取代,以便在创建 exchange 之前更早地处理转发头信息。如果仍然配置了该过滤器,它将从过滤器列表中移除,并改用 ForwardedHeaderTransformer

安全注意事项

由于应用程序无法确定转发头(forwarded headers)是由代理按预期添加的,还是由恶意客户端添加的,因此在处理转发头时存在安全方面的考量。正因如此,应配置位于信任边界处的代理,以移除来自外部的不可信转发流量。您还可以将 ForwardedHeaderTransformer 配置为 removeOnly=true,在这种情况下,它会移除这些头信息但不会使用它们。spring-doc.cadn.net.cn

过滤器

WebHandler API 中,您可以使用 WebFilter 在过滤器链的其余部分和目标 WebHandler 处理之前和之后应用拦截式逻辑。当使用 WebFlux 配置 时,注册 WebFilter 非常简单,只需将其声明为 Spring Bean,并(可选地)通过在 Bean 声明上使用 @Order 或实现 Ordered 来表达优先级。spring-doc.cadn.net.cn

CORS(跨域资源共享)

Spring WebFlux 通过控制器上的注解提供了细粒度的 CORS 配置支持。然而,当你将其与 Spring Security 一起使用时,我们建议依赖内置的 CorsFilter,该过滤器必须排在 Spring Security 过滤器链之前。spring-doc.cadn.net.cn

请参阅关于 CORS 的章节以及 CORS WebFilter 以获取更多详细信息。spring-doc.cadn.net.cn

URL 处理器

你可能希望你的控制器端点能够匹配 URL 路径中带有或不带尾部斜杠的路由。 例如,"GET /home" 和 "GET /home/" 都应由使用 @GetMapping("/home") 注解的控制器方法处理。spring-doc.cadn.net.cn

Spring 提供了 UrlHandlerFilter,用于从 URL 路径中移除末尾的斜杠,以确保对带或不带末尾斜杠的路径具有一致的视图。 这对于避免基于 URL 的授权决策与 Web 框架的请求映射之间出现不匹配非常重要。 该过滤器可以通过以下几种方式之一来移除末尾的斜杠:spring-doc.cadn.net.cn

以下是如何为博客应用程序实例化和配置一个UrlHandlerFilter的方法:spring-doc.cadn.net.cn

UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
		// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
		.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
		// will mutate the request to "/admin/user/account/" and make it as "/admin/user/account"
		.trailingSlashHandler("/admin/**").mutateRequest()
		.build();
val urlHandlerFilter = UrlHandlerFilter
	// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
	.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
	// will mutate the request to "/admin/user/account/" and make it as "/admin/user/account"
	.trailingSlashHandler("/admin/**").mutateRequest()
	.build()

请牢记以下内容:spring-doc.cadn.net.cn

  • 根路径 "/" 被排除在尾部斜杠处理之外。spring-doc.cadn.net.cn

  • @RequestMapping("/") 会在类级别的映射路径末尾添加一个斜杠,因此在启用了末尾斜杠处理时将无法匹配;请改用 @RequestMapping(不指定 path 属性)。spring-doc.cadn.net.cn

异常

WebHandler API 中,您可以使用 WebExceptionHandler 来处理来自 WebFilter 实例链和目标 WebHandler 的异常。当使用 WebFlux 配置 时,注册 WebExceptionHandler 非常简单,只需将其声明为 Spring Bean,并(可选地)通过在 Bean 声明上使用 @Order 或实现 Ordered 来表达优先级。spring-doc.cadn.net.cn

下表描述了可用的 WebExceptionHandler 实现:spring-doc.cadn.net.cn

异常处理器 描述

ResponseStatusExceptionHandlerspring-doc.cadn.net.cn

提供对类型为 ResponseStatusException 的异常的处理,方法是将响应设置为该异常的 HTTP 状态码。spring-doc.cadn.net.cn

WebFluxResponseStatusExceptionHandlerspring-doc.cadn.net.cn

ResponseStatusExceptionHandler 的扩展,还可以确定任何异常上 @ResponseStatus 注解所指定的 HTTP 状态码。spring-doc.cadn.net.cn

此处理器在WebFlux 配置中声明。spring-doc.cadn.net.cn

编解码器

spring-webspring-core 模块通过带有响应式流(Reactive Streams)背压机制的非阻塞 I/O,提供了将字节内容与高层对象之间进行序列化和反序列化的支持。以下内容描述了该支持:spring-doc.cadn.net.cn

spring-core 模块提供了 byte[]ByteBufferDataBufferResourceString 的编码器和解码器实现。spring-web 模块则提供了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 等编码器和解码器,以及仅用于 Web 的 HTTP 消息读取器和写入器实现,用于处理表单数据、多部分(multipart)内容、服务器发送事件(server-sent events)等。spring-doc.cadn.net.cn

ClientCodecConfigurerServerCodecConfigurer 通常用于配置和自定义应用程序中使用的编解码器。请参阅有关配置HTTP 消息编解码器的部分。spring-doc.cadn.net.cn

Jackson JSON

当 Jackson 库存在时,JSON 和二进制 JSON(Smile)均受支持。spring-doc.cadn.net.cn

Jackson2Decoder 的工作方式如下:spring-doc.cadn.net.cn

  • Jackson 的异步、非阻塞解析器用于将字节块流聚合为多个 TokenBuffer,每个 1 代表一个 JSON 对象。spring-doc.cadn.net.cn

  • 每个 TokenBuffer 都会被传递给 Jackson 的 ObjectMapper,以创建一个更高层次的对象。spring-doc.cadn.net.cn

  • 当解码为单值发布者(例如 Mono)时,会有一个 TokenBufferspring-doc.cadn.net.cn

  • 在解码为多值发布者(例如 Flux)时,一旦接收到足够构成一个完整对象的字节,每个 TokenBuffer 就会立即传递给 ObjectMapper。输入内容可以是一个 JSON 数组,也可以是任意 行分隔的 JSON 格式,例如 NDJSON、 JSON Lines 或 JSON 文本序列。spring-doc.cadn.net.cn

Jackson2Encoder 的工作方式如下:spring-doc.cadn.net.cn

  • 对于单值发布者(例如 Mono),只需通过 ObjectMapper 对其进行序列化即可。spring-doc.cadn.net.cn

  • 对于具有 application/json 类型的多值发布者,默认使用 Flux#collectToList() 收集所有值,然后对生成的集合进行序列化。spring-doc.cadn.net.cn

  • 对于具有流式媒体类型(例如 application/x-ndjsonapplication/stream+x-jackson-smile)的多值发布者,请使用行分隔 JSON 格式对每个值分别进行编码、写入和刷新。其他流式媒体类型也可以向编码器注册。spring-doc.cadn.net.cn

  • 对于 SSE,每次事件都会调用 Jackson2Encoder,并且会刷新输出以确保及时传递。spring-doc.cadn.net.cn

默认情况下,Jackson2EncoderJackson2Decoder 都不支持 String 类型的元素。相反,默认假定字符串或字符串序列代表已序列化的 JSON 内容,应由 CharSequenceEncoder 进行渲染。如果你需要从 Flux<String> 渲染一个 JSON 数组,请使用 Flux#collectToList() 并编码为 Mono<List<String>>spring-doc.cadn.net.cn

表单数据

FormHttpMessageReaderFormHttpMessageWriter 支持解码和编码 application/x-www-form-urlencoded 内容。spring-doc.cadn.net.cn

在服务器端,当表单内容经常需要从多个位置访问时, ServerWebExchange 提供了一个专用的 getFormData() 方法,该方法通过 FormHttpMessageReader 解析内容,然后缓存结果以供重复访问。 请参阅 表单数据,位于 WebHandler API 部分。spring-doc.cadn.net.cn

一旦使用了 getFormData(),就无法再从请求体中读取原始的原始内容。因此,应用程序应始终通过 ServerWebExchange 来访问缓存的表单数据,而不是直接读取原始请求体。spring-doc.cadn.net.cn

文件上传

MultipartHttpMessageReaderMultipartHttpMessageWriter 支持解码和编码"multipart/form-data"、"multipart/mixed"以及"multipart/related"内容。 随后,MultipartHttpMessageReader 将实际解析工作委托给另一个 HttpMessageReader,将其解析为 Flux<Part>,然后简单地将各部分收集到 MultiValueMap 中。 默认情况下,使用的是 DefaultPartHttpMessageReader,但可以通过 ServerCodecConfigurer 进行更改。 有关 DefaultPartHttpMessageReader 的更多信息,请参阅 DefaultPartHttpMessageReader 的 Javadocspring-doc.cadn.net.cn

在服务器端,当需要从多个位置访问多部分表单内容时,ServerWebExchange 提供了一个专用的 getMultipartData() 方法,该方法通过 MultipartHttpMessageReader 解析内容,然后缓存结果以供重复访问。 请参阅 多部分数据,位于 WebHandler API 部分。spring-doc.cadn.net.cn

一旦使用了 getMultipartData(),就无法再从请求体中读取原始的原始内容。因此,应用程序必须始终一致地使用 getMultipartData() 来实现对各部分(parts)的重复、类似 Map 的访问;否则,应依赖 SynchronossPartHttpMessageReaderFlux<Part> 进行一次性访问。spring-doc.cadn.net.cn

协议缓冲区

ProtobufEncoderProtobufDecoder 支持对 com.google.protobuf.Message 类型进行解码和编码,内容类型包括 "application/x-protobuf"、"application/octet-stream" 以及 "application/vnd.google.protobuf"。如果在内容类型中附带了 "delimited" 参数(例如 "application/x-protobuf;delimited=true"),它们还支持值的流式处理。这需要依赖 "com.google.protobuf:protobuf-java" 库,版本需为 3.29 或更高。spring-doc.cadn.net.cn

ProtobufJsonDecoderProtobufJsonEncoder 变体支持在 Protobuf 消息与 JSON 文档之间进行读写操作。 它们需要 "com.google.protobuf:protobuf-java-util" 依赖项。请注意,JSON 变体不支持读取消息流, 有关更多详细信息,请参阅 ProtobufJsonDecoder 的 Javadocspring-doc.cadn.net.cn

限制

可以为那些对部分或全部输入流进行缓冲的 DecoderHttpMessageReader 实现配置一个内存中缓冲字节数的最大限制。 在某些情况下,缓冲的发生是因为输入被聚合并表示为单个对象——例如,带有 @RequestBody byte[] 的控制器方法、x-www-form-urlencoded 数据等。 在流式处理场景中,当对输入流进行拆分时(例如,分隔符分隔的文本、JSON 对象流等),也会发生缓冲。 对于这些流式处理的情况,该限制适用于流中单个对象所关联的字节数。spring-doc.cadn.net.cn

要配置缓冲区大小,您可以检查给定的 DecoderHttpMessageReader 是否公开了 maxInMemorySize 属性,如果公开了该属性,其 Javadoc 中将包含有关默认值的详细信息。在服务器端,ServerCodecConfigurer 提供了一个统一的位置来设置所有编解码器,请参阅HTTP 消息编解码器。在客户端,所有编解码器的限制可以在WebClient.Builder 中进行修改。spring-doc.cadn.net.cn

对于多部分(Multipart)解析maxInMemorySize 属性用于限制非文件部分的大小。对于文件部分,该属性决定了将该部分写入磁盘的阈值。对于写入磁盘的文件部分,还有一个额外的 maxDiskUsagePerPart 属性,用于限制每个部分所占用的磁盘空间。此外,还有一个 maxParts 属性,用于限制多部分请求中各部分的总数量。 在 WebFlux 中配置上述三个属性时,你需要向 MultipartHttpMessageReader 提供一个预先配置好的 ServerCodecConfigurer 实例。spring-doc.cadn.net.cn

流式处理

在向 HTTP 响应进行流式传输时(例如 text/event-streamapplication/x-ndjson),定期发送数据非常重要,以便能够更早而非更晚地可靠检测到客户端断开连接。这种发送可以是一个仅包含注释的空 SSE 事件,或者任何其他实际上可作为心跳机制的“无操作”(no-op)数据。spring-doc.cadn.net.cn

DataBuffer

DataBuffer 是 WebFlux 中字节缓冲区的表示形式。有关此内容的更多详细信息,请参阅本参考文档 Spring Core 部分中的数据缓冲区和编解码器一节。需要理解的关键点是,在某些服务器(如 Netty)上,字节缓冲区是经过池化并采用引用计数的,必须在使用完毕后予以释放,以避免内存泄漏。spring-doc.cadn.net.cn

WebFlux 应用程序通常无需关注此类问题,除非它们直接消费或生成数据缓冲区(而不是依赖编解码器在高层对象与数据之间进行转换),或者除非它们选择创建自定义编解码器。对于这些情况,请参阅数据缓冲区和编解码器中的相关信息,特别是使用 DataBuffer一节。spring-doc.cadn.net.cn

日志记录

DEBUG 级别的日志记录在 Spring WebFlux 中设计得简洁、精炼且对人类友好。它聚焦于那些反复有用的关键信息,而非仅在调试特定问题时才有用的其他信息。spring-doc.cadn.net.cn

TRACE 级别的日志记录通常遵循与 DEBUG 相同的原则(例如,也不应像“消防水带”一样输出大量日志),但可用于调试任何问题。此外,某些日志消息在 TRACE 级别下可能会比在 DEBUG 级别下显示更详细的细节。spring-doc.cadn.net.cn

良好的日志记录源于使用日志的经验。如果您发现任何不符合所述目标的内容,请告知我们。spring-doc.cadn.net.cn

日志 ID

在 WebFlux 中,单个请求可能会在多个线程上执行,因此线程 ID 对于关联属于特定请求的日志消息并无帮助。这就是为什么 WebFlux 的日志消息默认会加上一个请求专属的 ID 作为前缀。spring-doc.cadn.net.cn

在服务器端,日志 ID 存储在 ServerWebExchange 属性中 (LOG_ID_ATTRIBUTE), 而基于该 ID 的完整格式化前缀可从 ServerWebExchange#getLogPrefix() 获取。在 WebClient 端,日志 ID 存储在 ClientRequest 属性中 (LOG_ID_ATTRIBUTE) ,而完整的格式化前缀可从 ClientRequest#logPrefix() 获取。spring-doc.cadn.net.cn

敏感数据

DEBUGTRACE 日志记录可能会记录敏感信息。因此,表单参数和请求头默认会被屏蔽,您必须显式启用才能完整记录它们。spring-doc.cadn.net.cn

以下示例展示了如何对服务器端请求进行此操作:spring-doc.cadn.net.cn

@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {

	@Override
	public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
		configurer.defaultCodecs().enableLoggingRequestDetails(true);
	}
}
@Configuration
@EnableWebFlux
class MyConfig : WebFluxConfigurer {

	override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
		configurer.defaultCodecs().enableLoggingRequestDetails(true)
	}
}

以下示例展示了如何对客户端请求进行此操作:spring-doc.cadn.net.cn

Consumer<ClientCodecConfigurer> consumer = configurer ->
		configurer.defaultCodecs().enableLoggingRequestDetails(true);

WebClient webClient = WebClient.builder()
		.exchangeStrategies(strategies -> strategies.codecs(consumer))
		.build();
val consumer: (ClientCodecConfigurer) -> Unit  = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) }

val webClient = WebClient.builder()
		.exchangeStrategies({ strategies -> strategies.codecs(consumer) })
		.build()

追加器

像 SLF4J 和 Log4J 2 这样的日志记录库提供了异步记录器,以避免阻塞。尽管这些异步记录器自身也存在一些缺点,例如可能会丢弃那些无法加入日志队列的消息,但它们目前仍是响应式、非阻塞应用程序中可用的最佳选择。spring-doc.cadn.net.cn

自定义编解码器

应用程序可以注册自定义编解码器,以支持额外的媒体类型,或实现默认编解码器不支持的特定行为。spring-doc.cadn.net.cn

开发人员指定的某些配置选项会被强制应用到默认编解码器上。 自定义编解码器可能希望有机会与这些偏好设置保持一致, 例如强制实施缓冲限制记录敏感数据spring-doc.cadn.net.cn

以下示例展示了如何对客户端请求进行此操作:spring-doc.cadn.net.cn

WebClient webClient = WebClient.builder()
		.codecs(configurer -> {
			CustomDecoder decoder = new CustomDecoder();
			configurer.customCodecs().registerWithDefaultConfig(decoder);
		})
		.build();
val webClient = WebClient.builder()
		.codecs({ configurer ->
			val decoder = CustomDecoder()
			configurer.customCodecs().registerWithDefaultConfig(decoder)
		 })
		.build()