对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
响应式核心
该模块包含以下对反应式 Web 的基本支持
应用:spring-web
-
对于服务器请求处理,有两个级别的支持。
-
HttpHandler:HTTP 请求处理的基本协定 非阻塞 I/O 和反应流背压,以及用于 Reactor Netty 的适配器, Undertow、Tomcat、Jetty 和任何 Servlet 容器。
-
WebHandler
API:稍高级的通用 Web API,用于 请求处理,除此之外,还有具体的编程模型,如 Annotated 构建控制器和功能端点。
-
-
对于客户端,有一个基本的 Contract 来执行 HTTP 具有非阻塞 I/O 和反应流背压的请求,以及用于 Reactor Netty、反应式 Jetty HttpClient 和 Apache HttpComponents 的适配器。 应用程序中使用的更高级别的 WebClient 建立在这个基本契约之上。
ClientHttpConnector
-
对于客户端和服务器,用于序列化的编解码器和 HTTP 请求和响应内容的反序列化。
HttpHandler
HttpHandler 是一个简单的协定,具有处理请求和响应的单一方法。是的 有意最小化,其主要且唯一目的是成为最小抽象 通过不同的 HTTP 服务器 API 进行。
下表描述了支持的服务器 API:
服务器名称 | 使用的服务器 API | Reactive Streams 支持 |
---|---|---|
网 |
Netty API |
|
Undertow |
Undertow API |
spring-web: Undertow 到 Reactive Streams 桥 |
Tomcat |
Servlet 非阻塞 I/O;Tomcat API 读取和写入 ByteBuffers 与 byte[] |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥 |
Jetty |
Servlet 非阻塞 I/O;Jetty API 写入 ByteBuffers 与 byte[] |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥 |
Servlet 容器 |
Servlet 非阻塞 I/O |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥 |
下表描述了服务器依赖项(另请参阅支持的版本):
服务器名称 | 组 ID | 项目名称 |
---|---|---|
Reactor Netty |
io.projectreactor.netty |
反应器-NETTY |
Undertow |
io.undertow |
undertow-core |
Tomcat |
org.apache.tomcat.embed |
tomcat-embed-core |
Jetty |
org.eclipse.jetty 网站 |
jetty 服务器、jetty-servlet |
下面的代码片段显示了将适配器与每个服务器 API 一起使用:HttpHandler
Reactor Netty
-
Java
-
Kotlin
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()
Undertow
-
Java
-
Kotlin
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()
Tomcat
-
Java
-
Kotlin
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()
Jetty
-
Java
-
Kotlin
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 容器
要作为 WAR 部署到任何 Servlet 容器,你可以在 WAR 中扩展并包含 AbstractReactiveWebInitializer
。该类将 with 包装并注册
that 作为 .HttpHandler
ServletHttpHandlerAdapter
Servlet
WebHandler
应用程序接口
该包基于 HttpHandler
协定构建
提供一个通用的 Web API,用于通过多个 WebExceptionHandler
、多个 WebFilter
和单个 WebHandler
组件的链处理请求。链条可以
通过简单地指向自动检测组件的 Spring,和/或通过注册组件来组合在一起
与构建器。org.springframework.web.server
WebHttpHandlerBuilder
ApplicationContext
虽然 API 的目标很简单,即抽象出不同 HTTP 服务器的使用,但 API 旨在提供 Web 应用程序中常用的更广泛的功能集
如:HttpHandler
WebHandler
-
具有属性的用户会话。
-
请求属性。
-
已解决或针对请求。
Locale
Principal
-
访问已解析和缓存的表单数据。
-
多部分数据的抽象。
-
和更多..
特殊 bean 类型
下表列出了可以在
Spring ApplicationContext,或者可以直接使用它注册:WebHttpHandlerBuilder
Bean 名称 | Bean 类型 | 计数 | 描述 |
---|---|---|---|
<任意> |
|
0..N |
为实例链和 target 中的异常提供处理。有关更多详细信息,请参阅 异常。 |
<任意> |
|
0..N |
将拦截样式逻辑应用于过滤器链的其余部分之前和之后,并且
目标 .有关更多详细信息,请参阅筛选器。 |
|
|
1 |
请求的处理程序。 |
|
|
0..1 |
通过 上的方法公开的实例的管理器。 默认情况下。 |
|
|
0..1 |
要访问实例以解析表单数据和多部分数据,则
通过 上的方法公开。 默认情况下。 |
|
|
0..1 |
通过 上的方法公开的解析程序。 默认情况下。 |
|
|
0..1 |
用于处理转发类型的标头,可以通过提取和删除它们,或者仅删除它们。 默认情况下不使用。 |
表单数据
ServerWebExchange
公开以下用于访问表单数据的方法:
-
Java
-
Kotlin
Mono<MultiValueMap<String, String>> getFormData();
suspend fun getFormData(): MultiValueMap<String, String>
使用 configured 来解析表单数据
() 转换为 .默认情况下,配置为供 Bean 使用
(请参阅 Web 处理程序 API)。DefaultServerWebExchange
HttpMessageReader
application/x-www-form-urlencoded
MultiValueMap
FormHttpMessageReader
ServerCodecConfigurer
多部分数据
ServerWebExchange
公开以下用于访问分段数据的方法:
-
Java
-
Kotlin
Mono<MultiValueMap<String, Part>> getMultipartData();
suspend fun getMultipartData(): MultiValueMap<String, Part>
使用配置的将 、 和 内容解析为 .
默认情况下,这是 ,它没有任何第三方
依赖。
或者,也可以使用基于 Synchronoss NIO Multipart 库的 the。
两者都是通过 bean 配置的
(请参阅 Web 处理程序 API)。DefaultServerWebExchange
HttpMessageReader<MultiValueMap<String, Part>>
multipart/form-data
multipart/mixed
multipart/related
MultiValueMap
DefaultPartHttpMessageReader
SynchronossPartHttpMessageReader
ServerCodecConfigurer
要以流式方式解析多部分数据,你可以使用 return from the 而不是使用 ,因为这意味着 -like 访问
按名称分配给各个部分,因此需要完整解析多个部分数据。
相比之下,您可以使用 将内容解码为 without
收集到 .Flux<PartEvent>
PartEventHttpMessageReader
@RequestPart
Map
@RequestBody
Flux<PartEvent>
MultiValueMap
请求头转发
当请求通过代理(例如负载均衡器)、主机、端口和 方案可能会发生变化。从客户的角度来看,这使得创建指向正确 host、port 和 scheme。
RFC 7239 定义了 HTTP 标头
代理可用于提供有关原始请求的信息。还有其他
非标准标头,包括 、 和 .Forwarded
X-Forwarded-Host
X-Forwarded-Port
X-Forwarded-Proto
X-Forwarded-Ssl
X-Forwarded-Prefix
ForwardedHeaderTransformer
是一个组件,用于修改
请求,然后删除这些标头。如果您声明
it 作为名为 的 bean ,它将被检测和使用。forwardedHeaderTransformer
转发的 Headers 存在安全注意事项,因为应用程序无法知道
标头是由代理按预期添加的,还是由恶意客户端添加的。这就是为什么
应将信任边界的代理配置为删除传入的不受信任的转发流量
从外面。您还可以配置 with ,在这种情况下,它会删除但不使用 Headers。ForwardedHeaderTransformer
removeOnly=true
在 5.1 中已被弃用并被取代,因此请求头转发可以更早地处理,在
Exchange 已创建。如果仍然配置了过滤器,则会将其从
filters,并改用 。ForwardedHeaderFilter ForwardedHeaderTransformer ForwardedHeaderTransformer |
过滤器
在 WebHandler
API 中,您可以使用 a 来应用拦截样式
过滤器其余处理链和目标之前和之后的逻辑 。使用 WebFlux 配置时,注册一个
将其声明为 Spring Bean 并(可选地)使用 on 表示优先级
bean 声明或通过实现 .WebFilter
WebHandler
WebFilter
@Order
Ordered
CORS
Spring WebFlux 通过对 CORS 配置的注释提供细粒度的支持
控制器。但是,当您将其与 Spring Security 一起使用时,我们建议依赖 built-in ,它必须在 Spring Security 的过滤器链之前订购。CorsFilter
有关更多详细信息,请参阅 CORS 和 CORS WebFilter
部分。
异常
在 WebHandler
API 中,你可以使用 a 来处理
实例链和目标 的异常 。当使用 WebFlux Config 时,注册 a 就像将其声明为
Spring bean 和(可选)通过在 bean 声明上使用来表示优先级,或者
通过实施 .WebExceptionHandler
WebFilter
WebHandler
WebExceptionHandler
@Order
Ordered
下表介绍了可用的实施:WebExceptionHandler
异常处理程序 | 描述 |
---|---|
|
通过将响应设置为异常的 HTTP 状态代码,提供对 |
|
的扩展也可以确定 HTTP 状态
code 的注释。 此处理程序在 WebFlux Config 中声明。 |
Codec
和 模块提供对序列化和
通过非阻塞 I/O 将字节内容反序列化到更高级别的对象或从更高级别的对象反序列化
反应流背压。下面介绍了此支持:spring-web
spring-core
-
HttpMessageReader
和HttpMessageWriter
是 Contract 对 HTTP 消息内容进行编码和解码。 -
可以包装 an 以适应在 web 中使用 application,而 a 可以用 .
Encoder
EncoderHttpMessageWriter
Decoder
DecoderHttpMessageReader
-
DataBuffer
抽象出不同的 字节缓冲区表示形式(例如 Netty 、 等)和 is 所有编解码器都适用于什么。请参阅 Data Buffers and Codecs 中的 “Spring Core” 部分,了解有关此主题的更多信息。ByteBuf
java.nio.ByteBuffer
该模块提供 、 、 、 以及 编码器 和 解码器 实现。该模块提供 Jackson
JSON、Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器以及
用于表单数据、多部分内容、
server-sent 事件等。spring-core
byte[]
ByteBuffer
DataBuffer
Resource
String
spring-web
ClientCodecConfigurer
,通常用于配置和
自定义要在应用程序中使用的编解码器。请参阅 配置 HTTP 消息编解码器 部分。ServerCodecConfigurer
Jackson JSON
JSON 和二进制 JSON (Smile) 是 当存在 Jackson 库时,两者都受支持。
工作原理如下:Jackson2Decoder
-
Jackson 的异步、非阻塞解析器用于聚合字节块流 into 的 ,每个都表示一个 JSON 对象。
TokenBuffer
-
每个对象都传递给 Jackson's 以创建更高级别的对象。
TokenBuffer
ObjectMapper
-
解码为单值发布者(例如 )时,有一个 .
Mono
TokenBuffer
-
当解码到多值发布者(例如 )时,每个都会传递给 一旦收到足够多的字节用于完全形成的对象。这 input 内容可以是 JSON 数组,也可以是任何以行分隔的 JSON 格式,例如 NDJSON, JSON 行或 JSON 文本序列。
Flux
TokenBuffer
ObjectMapper
工作原理如下:Jackson2Encoder
-
对于单值发布者(例如 ),只需通过 .
Mono
ObjectMapper
-
对于具有 的多值发布者,默认情况下,使用 收集值,然后序列化生成的集合。
application/json
Flux#collectToList()
-
对于具有 or 、 encode、write 和 等流媒体类型的多值发布者 使用以行分隔的 JSON 格式单独刷新每个值。其他 流媒体类型可以注册到编码器。
application/x-ndjson
application/stream+x-jackson-smile
-
对于 SSE,将按事件调用 ,并刷新输出以确保 毫不拖延地交货。
Jackson2Encoder
默认情况下,both 和 都不支持 .相反,默认假设是一个字符串或一系列字符串
表示序列化的 JSON 内容,由 .如果
您需要从 、use 和
编码 . |
表单数据
FormHttpMessageReader
并支持对内容进行解码和编码。FormHttpMessageWriter
application/x-www-form-urlencoded
在经常需要从多个位置访问表单内容的服务器端,提供解析内容的专用方法
,然后缓存结果以供重复访问。
请参阅 WebHandler
API 部分中的表单数据。ServerWebExchange
getFormData()
FormHttpMessageReader
使用后,将无法再从
请求正文。因此,应用程序需要始终如一地访问缓存的表单数据,而不是从原始请求正文中读取数据。getFormData()
ServerWebExchange
多部分
MultipartHttpMessageReader
并支持解码和
对“multipart/form-data”、“multipart/mixed”和“multipart/related”内容进行编码。
反过来,将实际解析委托给另一个 a,然后简单地将部分收集到 .
默认情况下,使用 the,但这可以通过 .
有关 的更多信息,请参阅 DefaultPartHttpMessageReader
的 javadoc。MultipartHttpMessageWriter
MultipartHttpMessageReader
HttpMessageReader
Flux<Part>
MultiValueMap
DefaultPartHttpMessageReader
ServerCodecConfigurer
DefaultPartHttpMessageReader
在服务器端,可能需要从多个访问多部分表单内容
places 提供了一个专用的解析方法
内容,然后缓存结果以供重复访问。
请参阅 WebHandler
API 部分中的 Multipart Data。ServerWebExchange
getMultipartData()
MultipartHttpMessageReader
使用后,将无法再从
请求正文。因此,应用程序必须始终如一地使用对部分的重复、类似映射的访问,或者依赖 进行对 的一次性访问。getMultipartData()
getMultipartData()
SynchronossPartHttpMessageReader
Flux<Part>
限制
Decoder
以及缓冲部分或全部 input 的实现
stream 可以配置对内存中缓冲的最大字节数的限制。
在某些情况下,之所以发生缓冲,是因为输入被聚合并表示为单个
object — 例如,具有 、 data 等的控制器方法。流式处理也可能发生缓冲,当
拆分输入流 — 例如,分隔文本、JSON 对象流,以及
等等。对于这些流式处理情况,限制适用于关联的字节数
在流中有一个对象。HttpMessageReader
@RequestBody byte[]
x-www-form-urlencoded
要配置缓冲区大小,您可以检查给定的 or 公开属性,如果是,则 Javadoc 将包含有关 default 的详细信息
值。在服务器端,提供从 where 到
设置所有编解码器,请参阅 HTTP 消息编解码器。在客户端,对
所有编解码器都可以在 WebClient.Builder 中更改。Decoder
HttpMessageReader
maxInMemorySize
ServerCodecConfigurer
对于 Multipart 解析,属性限制
非文件部分的大小。对于文件部分,它确定部分达到的阈值
已写入磁盘。对于写入磁盘的文件部分,有一个附加属性来限制每个部分的磁盘空间量。还有
用于限制 Multipart 请求中段总数的属性。
要在 WebFlux 中配置所有三个实例,您需要提供 的预配置实例 to 。maxInMemorySize
maxDiskUsagePerPart
maxParts
MultipartHttpMessageReader
ServerCodecConfigurer
流
流式传输到 HTTP 响应(例如 , , )时,必须定期发送数据,以便
尽早可靠地检测断开连接的客户端。这样的发送可以是
仅注释、空 SSE 事件或任何其他“无操作”数据,这些数据实际上可以用作
心跳。text/event-stream
application/x-ndjson
DataBuffer
DataBuffer
是 WebFlux 中字节缓冲区的表示形式。Spring Core 的
此参考在 Data Buffers and Codecs 一节中提供了更多相关信息。要理解的关键点是,在某些
像 Netty 这样的服务器,字节缓冲区是池化的和引用计数的,并且必须释放
当使用以避免内存泄漏时。
WebFlux 应用程序通常不需要关心此类问题,除非它们 直接使用或生成数据缓冲区,而不是依赖编解码器转换为 以及更高级别的对象,或者除非他们选择创建自定义编解码器。对于这样的 情况请查看 Data Buffers 和 Codecs、 尤其是 使用 DataBuffer 的部分。
Logging
DEBUG
Spring WebFlux 中的级别日志记录被设计为紧凑、最小和
人性化。它侧重于有用的高价值信息
over again 与仅在调试特定问题时有用的其他 cookie 进行比较。
TRACE
级别日志记录通常遵循与 (例如,还将
不应是 Firehose),但可用于调试任何问题。此外,一些日志
消息可能会在 VS 中显示不同级别的详细信息。DEBUG
TRACE
DEBUG
良好的日志记录来自使用日志的经验。如果您发现任何 未达到既定目标,请告诉我们。
日志 ID
在 WebFlux 中,单个请求可以在多个线程上运行,线程 ID 对于关联属于特定请求的日志消息没有用。这就是为什么 默认情况下,WebFlux 日志消息以特定于请求的 ID 为前缀。
在服务器端,日志 ID 存储在属性
(LOG_ID_ATTRIBUTE
)、
而基于该 ID 的完全格式化前缀可从 中获得。在一端,日志 ID 存储在属性
(LOG_ID_ATTRIBUTE
)
,而完全格式化的前缀可从 获得。ServerWebExchange
ServerWebExchange#getLogPrefix()
WebClient
ClientRequest
ClientRequest#logPrefix()
敏感数据
DEBUG
logging 可以记录敏感信息。这就是为什么表单参数和
默认情况下,标头是屏蔽的,您必须显式启用其日志记录。TRACE
以下示例显示了如何对服务器端请求执行此操作:
-
Java
-
Kotlin
@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)
}
}
以下示例说明如何对客户端请求执行此操作:
-
Java
-
Kotlin
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)提供了异步记录器,可避免 阻塞。虽然这些有其自身的缺点,例如可能会丢弃消息 无法排队进行日志记录,它们是目前最好的可用选项 用于反应式、非阻塞应用程序。
自定义编解码器
应用程序可以注册自定义编解码器以支持其他媒体类型、 或默认编解码器不支持的特定行为。
以下示例说明如何对客户端请求执行此操作:
-
Java
-
Kotlin
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()