Reactive Core

spring-web 模組包含以下用於響應式 Web 應用程式的基礎支援

  • 對於伺服器請求處理,有兩層支援。

    • HttpHandler:使用非阻塞 I/O 和 Reactive Streams 反壓處理 HTTP 請求的基本契約,以及對 Reactor Netty、Tomcat、Jetty 和任何 Servlet 容器的介面卡。

    • WebHandler API:更高層、通用的 Web API,用於請求處理,其上構建了具體的程式設計模型,如帶註解的控制器和函式式端點。

  • 在客戶端,有一個基本的 ClientHttpConnector 契約,用於使用非阻塞 I/O 和 Reactive Streams 反壓執行 HTTP 請求,以及對 Reactor Netty、響應式 Jetty HttpClientApache HttpComponents 的介面卡。應用程式中使用的更高級別的 WebClient 構建在此基本契約之上。

  • 對於客戶端和伺服器,有用於 HTTP 請求和響應內容序列化和反序列化的 編解碼器

HttpHandler

HttpHandler 是一個簡單的契約,只有一個方法來處理請求和響應。它刻意保持最小化,其主要也是唯一目的是作為不同 HTTP 伺服器 API 的最小抽象。

下表描述了支援的伺服器 API

伺服器名稱 使用的伺服器 API Reactive Streams 支援

Netty

Netty API

Reactor Netty

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 Artifact 名稱

Reactor Netty

io.projectreactor.netty

reactor-netty

Tomcat

org.apache.tomcat.embed

tomcat-embed-core

Jetty

org.eclipse.jetty

jetty-server, jetty-servlet

以下程式碼片段展示瞭如何將 HttpHandler 介面卡與每個伺服器 API 配合使用。

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

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 = ...
JettyCoreHttpHandlerAdapter adapter = new JettyCoreHttpHandlerAdapter(handler);

Server server = new Server();
server.setHandler(adapter);

ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);

server.start();
val handler: HttpHandler = ...
val adapter = JettyCoreHttpHandlerAdapter(handler)

val server = Server()
server.setHandler(adapter)

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

server.start()
在 Spring Framework 6.2 中,JettyHttpHandlerAdapter 已被棄用,轉而使用 JettyCoreHttpHandlerAdapter,後者直接與 Jetty 12 API 整合,無需 Servlet 層。

若要部署為 WAR 到 Servlet 容器,請使用 AbstractReactiveWebInitializer,透過 ServletHttpHandlerAdapterHttpHandler 適配到 Servlet

WebHandler API

org.springframework.web.server 包基於 HttpHandler 契約構建,提供了一個通用的 Web API,透過多個 WebExceptionHandler、多個 WebFilter 和一個 WebHandler 元件鏈來處理請求。該鏈可以透過 WebHttpHandlerBuilder 組裝,只需指向一個 Spring ApplicationContext,其中元件會被自動檢測,和/或透過構建器註冊元件。

雖然 HttpHandler 的簡單目標是抽象不同 HTTP 伺服器的使用,但 WebHandler API 旨在提供 Web 應用程式中常用的一組更廣泛的功能,例如

  • 帶有屬性的使用者會話。

  • 請求屬性。

  • 請求已解析的 LocalePrincipal

  • 訪問已解析和快取的表單資料。

  • 多部分資料的抽象。

  • 等等...

特殊 Bean 型別

下表列出了 WebHttpHandlerBuilder 可以在 Spring ApplicationContext 中自動檢測到或直接註冊的元件

Bean 名稱 Bean 型別 計數 描述

<任意>

WebExceptionHandler

0..N

WebFilter 例項鏈和目標 WebHandler 中的異常提供處理。有關更多詳細資訊,請參見 異常

<任意>

WebFilter

0..N

在過濾鏈的其餘部分和目標 WebHandler 之前和之後應用攔截式邏輯。有關更多詳細資訊,請參見 過濾器

webHandler

WebHandler

1

請求的處理程式。

webSessionManager

WebSessionManager

0..1

透過 ServerWebExchange 上的方法公開的 WebSession 例項管理器。預設為 DefaultWebSessionManager

serverCodecConfigurer

ServerCodecConfigurer

0..1

用於訪問 HttpMessageReader 例項,以解析表單資料和多部分資料,然後透過 ServerWebExchange 上的方法公開。預設為 ServerCodecConfigurer.create()

localeContextResolver

LocaleContextResolver

0..1

透過 ServerWebExchange 上的方法公開的 LocaleContext 解析器。預設為 AcceptHeaderLocaleContextResolver

forwardedHeaderTransformer

ForwardedHeaderTransformer

0..1

用於處理轉發型別頭,可以提取並刪除它們,也可以只刪除它們。預設不使用。

表單資料

ServerWebExchange 公開了以下用於訪問表單資料的方法

  • Java

  • Kotlin

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

DefaultServerWebExchange 使用配置的 HttpMessageReader 將表單資料(application/x-www-form-urlencoded)解析為 MultiValueMap。預設情況下,FormHttpMessageReader 配置為由 ServerCodecConfigurer bean 使用(參見 Web Handler API)。

多部分資料

ServerWebExchange 公開了以下用於訪問多部分資料的方法

  • Java

  • Kotlin

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

DefaultServerWebExchange 使用配置的 HttpMessageReader<MultiValueMap<String, Part>>multipart/form-datamultipart/mixedmultipart/related 內容解析為 MultiValueMap。預設情況下,這是 DefaultPartHttpMessageReader,它沒有任何第三方依賴。或者,可以使用 SynchronossPartHttpMessageReader,它基於 Synchronoss NIO Multipart 庫。兩者都透過 ServerCodecConfigurer bean 配置(參見 Web Handler API)。

要以流式方式解析多部分資料,可以使用 PartEventHttpMessageReader 返回的 Flux<PartEvent>,而不是使用 @RequestPart,因為後者意味著按名稱對單個部分進行類似 Map 的訪問,因此需要完整解析多部分資料。相反,可以使用 @RequestBody 將內容解碼為 Flux<PartEvent>,而無需收集到 MultiValueMap

轉發頭

當請求透過負載均衡器等代理時,主機、埠和方案可能會發生變化,這使得從客戶端角度建立指向正確主機、埠和方案的連結成為一項挑戰。

RFC 7239 定義了 Forwarded HTTP 頭,代理可以使用它來提供有關原始請求的資訊。

非標準頭

還有其他非標準頭,包括 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-PrefixX-Forwarded-For

X-Forwarded-Host

雖然不是標準,但 X-Forwarded-Host: <host> 是一個事實上的標準頭,用於將原始主機傳遞給下游伺服器。例如,如果向代理傳送一個 example.com/resource 的請求,該代理將請求轉發到 localhost:8080/resource,則可以傳送一個 X-Forwarded-Host: example.com 的頭,以告知伺服器原始主機是 example.com

X-Forwarded-Port

雖然不是標準,但 X-Forwarded-Port: <port> 是一個事實上的標準頭,用於將原始埠傳遞給下游伺服器。例如,如果向代理傳送一個 example.com/resource 的請求,該代理將請求轉發到 localhost:8080/resource,則可以傳送一個 X-Forwarded-Port: 443 的頭,以告知伺服器原始埠是 443

X-Forwarded-Proto

雖然不是標準,但 X-Forwarded-Proto: (https|http) 是一個事實上的標準頭,用於將原始協議(例如 https / http)傳遞給下游伺服器。例如,如果向代理傳送一個 example.com/resource 的請求,該代理將請求轉發到 localhost:8080/resource,則可以傳送一個 X-Forwarded-Proto: https 的頭,以告知伺服器原始協議是 https

X-Forwarded-Ssl

雖然不是標準,但 X-Forwarded-Ssl: (on|off) 是一個事實上的標準頭,用於將原始協議(例如 https / https)傳遞給下游伺服器。例如,如果向代理傳送一個 example.com/resource 的請求,該代理將請求轉發到 localhost:8080/resource,則可以傳送一個 X-Forwarded-Ssl: on 的頭,以告知伺服器原始協議是 https

X-Forwarded-Prefix

雖然不是標準,但 X-Forwarded-Prefix: <prefix> 是一個事實上的標準頭,用於將原始 URL 路徑字首傳遞給下游伺服器。

X-Forwarded-Prefix 的使用可能因部署場景而異,需要靈活地允許替換、刪除或前置目標伺服器的路徑字首。

場景 1:覆蓋路徑字首

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

字首是捕獲組 {path} 之前的路徑起始部分。對於代理,字首是 /api,而對於伺服器,字首是 /app1。在這種情況下,代理可以傳送 X-Forwarded-Prefix: /api,以使原始字首 /api 覆蓋伺服器字首 /app1

場景 2:刪除路徑字首

有時,應用程式可能希望刪除字首。例如,考慮以下代理到伺服器的對映

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

代理沒有字首,而應用程式 app1app2 分別有路徑字首 /app1/app2。代理可以傳送 X-Forwarded-Prefix: ,以使空字首覆蓋伺服器字首 /app1/app2

這種部署場景的一個常見情況是,每個生產應用伺服器都需要支付許可費用,並且最好在每個伺服器上部署多個應用程式以減少費用。另一個原因是在同一伺服器上執行更多應用程式,以共享伺服器執行所需的資源。

在這些場景中,應用程式需要一個非空的上下文根,因為同一伺服器上存在多個應用程式。但是,這不應該在公共 API 的 URL 路徑中可見,其中應用程式可以使用不同的子域,這提供了以下好處:

  • 增加安全性,例如同源策略

  • 應用程式的獨立擴充套件(不同域指向不同 IP 地址)

場景 3:插入路徑字首

在其他情況下,可能需要前置一個字首。例如,考慮以下代理到伺服器的對映

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

在這種情況下,代理的字首是 /api/app1,伺服器的字首是 /app1。代理可以傳送 X-Forwarded-Prefix: /api/app1,以使原始字首 /api/app1 覆蓋伺服器字首 /app1

X-Forwarded-For

X-Forwarded-For: <address> 是一個事實上的標準頭,用於將客戶端的原始 InetSocketAddress 傳遞給下游伺服器。例如,如果客戶端在 [fd00:fefe:1::4]192.168.0.1 的代理傳送請求,HTTP 請求中包含的“遠端地址”資訊將反映客戶端的實際地址,而不是代理的地址。

ForwardedHeaderTransformer

ForwardedHeaderTransformer 是一個元件,它根據轉發頭修改請求的主機、埠和方案,然後刪除這些頭。如果將其宣告為名為 forwardedHeaderTransformer 的 bean,它將被檢測並使用。

安全考慮

轉發頭存在安全考慮,因為應用程式無法知道這些頭是代理按預期新增的,還是惡意客戶端新增的。這就是為什麼信任邊界的代理應該配置為刪除來自外部的不受信任的轉發流量。您還可以將 ForwardedHeaderTransformer 配置為 removeOnly=true,在這種情況下它會刪除但不使用這些頭。

過濾器

WebHandler API 中,您可以使用 WebFilter 在過濾器處理鏈的其餘部分和目標 WebHandler 之前和之後應用攔截式邏輯。使用 WebFlux 配置 時,註冊 WebFilter 就像將其宣告為 Spring bean 並(可選地)透過在 bean 宣告上使用 @Order 或實現 Ordered 來表達優先順序一樣簡單。

CORS

Spring WebFlux 透過控制器上的註解提供對 CORS 配置的精細支援。但是,當您將其與 Spring Security 一起使用時,我們建議依賴內建的 CorsFilter,它必須在 Spring Security 過濾器鏈之前進行排序。

有關更多詳細資訊,請參見 CORS 部分和 CORS WebFilter

URL 處理程式

您可能希望控制器端點匹配 URL 路徑中帶或不帶尾隨斜槓的路由。例如,"GET /home" 和 "GET /home/" 都應該由使用 @GetMapping("/home") 註解的控制器方法處理。

將尾隨斜槓變體新增到所有對映宣告中並不是處理此用例的最佳方法。UrlHandlerFilter web 過濾器就是為此目的而設計的。它可以配置為

  • 當接收到帶有尾隨斜槓的 URL 時,用 HTTP 重定向狀態響應,將瀏覽器傳送到不帶尾隨斜槓的 URL 變體。

  • 改變請求,使其表現得好像請求是未帶尾隨斜槓傳送的,並繼續處理請求。

以下是如何為部落格應用程式例項化和配置 UrlHandlerFilter 的示例

  • Java

  • Kotlin

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

異常

WebHandler API 中,您可以使用 WebExceptionHandler 來處理 WebFilter 例項鏈和目標 WebHandler 中的異常。使用 WebFlux 配置 時,註冊 WebExceptionHandler 就像將其宣告為 Spring bean 並(可選地)透過在 bean 宣告上使用 @Order 或實現 Ordered 來表達優先順序一樣簡單。

下表描述了可用的 WebExceptionHandler 實現

異常處理程式 描述

ResponseStatusExceptionHandler

透過將響應設定為異常的 HTTP 狀態碼,為 ResponseStatusException 型別的異常提供處理。

WebFluxResponseStatusExceptionHandler

ResponseStatusExceptionHandler 的擴充套件,它還可以確定任何異常上 @ResponseStatus 註解的 HTTP 狀態碼。

此處理程式在 WebFlux 配置 中宣告。

編解碼器

spring-webspring-core 模組透過非阻塞 I/O 和 Reactive Streams 反壓,支援將位元組內容序列化和反序列化為高階物件。以下描述了此支援

  • EncoderDecoder 是獨立於 HTTP 對內容進行編碼和解碼的低階契約。

  • HttpMessageReaderHttpMessageWriter 是用於編碼和解碼 HTTP 訊息內容的契約。

  • Encoder 可以用 EncoderHttpMessageWriter 包裝以適應在 Web 應用程式中使用,而 Decoder 可以用 DecoderHttpMessageReader 包裝。

  • DataBuffer 抽象了不同的位元組緩衝區表示(例如,Netty ByteBufjava.nio.ByteBuffer 等),並且所有編解碼器都基於此工作。有關此主題的更多資訊,請參見“Spring Core”部分的資料緩衝區和編解碼器

spring-core 模組提供了 byte[]ByteBufferDataBufferResourceString 編碼器和解碼器實現。spring-web 模組提供了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 和其他編碼器和解碼器,以及用於表單資料、多部分內容、伺服器傳送事件等的 Web 專用 HTTP 訊息讀取器和寫入器實現。

ClientCodecConfigurerServerCodecConfigurer 通常用於配置和自定義應用程式中使用的編解碼器。請參見配置 HTTP 訊息編解碼器 的部分。

Jackson JSON

當存在 Jackson 庫時,JSON 和二進位制 JSON(Smile)都受支援。

Jackson2Decoder 的工作方式如下

  • Jackson 的非同步、非阻塞解析器用於將位元組塊流聚合為表示 JSON 物件的 TokenBuffer

  • 每個 TokenBuffer 都傳遞給 Jackson 的 ObjectMapper 以建立更高級別的物件。

  • 當解碼為單值釋出者(例如 Mono)時,有一個 TokenBuffer

  • 當解碼為多值釋出者(例如 Flux)時,一旦收到足夠形成完整物件的位元組,每個 TokenBuffer 就會傳遞給 ObjectMapper。輸入內容可以是 JSON 陣列,或任何行分隔 JSON 格式,如 NDJSON、JSON Lines 或 JSON Text Sequences。

Jackson2Encoder 的工作方式如下

  • 對於單值釋出者(例如 Mono),只需透過 ObjectMapper 對其進行序列化。

  • 對於帶有 application/json 的多值釋出者,預設情況下使用 Flux#collectToList() 收集值,然後序列化生成的集合。

  • 對於帶有流媒體型別(例如 application/x-ndjsonapplication/stream+x-jackson-smile)的多值釋出者,使用行分隔 JSON 格式單獨編碼、寫入和重新整理每個值。其他流媒體型別可以註冊到編碼器。

  • 對於 SSE,Jackson2Encoder 會針對每個事件呼叫,並重新整理輸出以確保及時交付。

預設情況下,Jackson2EncoderJackson2Decoder 都不支援 String 型別的元素。相反,預設假設字串或字串序列表示序列化的 JSON 內容,將由 CharSequenceEncoder 渲染。如果需要從 Flux<String> 渲染 JSON 陣列,請使用 Flux#collectToList() 並編碼 Mono<List<String>>

表單資料

FormHttpMessageReaderFormHttpMessageWriter 支援解碼和編碼 application/x-www-form-urlencoded 內容。

在伺服器端,表單內容通常需要從多個地方訪問,ServerWebExchange 提供了一個專用的 getFormData() 方法,透過 FormHttpMessageReader 解析內容,然後快取結果以供重複訪問。請參見 WebHandler API 部分的 表單資料

一旦使用了 getFormData(),就不能再從請求體中讀取原始原始內容。因此,應用程式應該始終透過 ServerWebExchange 訪問快取的表單資料,而不是從原始請求體中讀取。

多部分

MultipartHttpMessageReaderMultipartHttpMessageWriter 支援解碼和編碼“multipart/form-data”、“multipart/mixed”和“multipart/related”內容。反過來,MultipartHttpMessageReader 將實際解析委託給另一個 HttpMessageReader,以解析為 Flux<Part>,然後簡單地將部分收集到 MultiValueMap 中。預設情況下,使用 DefaultPartHttpMessageReader,但這可以透過 ServerCodecConfigurer 更改。有關 DefaultPartHttpMessageReader 的更多資訊,請參閱 DefaultPartHttpMessageReader 的 Javadoc

在伺服器端,多部分表單內容可能需要從多個地方訪問,ServerWebExchange 提供了一個專用的 getMultipartData() 方法,透過 MultipartHttpMessageReader 解析內容,然後快取結果以供重複訪問。請參見 WebHandler API 部分的 多部分資料

一旦使用了 getMultipartData(),就不能再從請求體中讀取原始原始內容。因此,應用程式必須始終使用 getMultipartData() 進行重複的、類似 map 的部分訪問,否則依賴於 SynchronossPartHttpMessageReader 進行一次性的 Flux<Part> 訪問。

Protocol Buffers

ProtobufEncoderProtobufDecoder 支援對 com.google.protobuf.Message 型別進行“application/x-protobuf”、“application/octet-stream”和“application/vnd.google.protobuf”內容的解碼和編碼。如果內容是隨內容型別(例如“application/x-protobuf;delimited=true”)接收/傳送,並且帶有“delimited”引數,它們還支援值的流。這需要“com.google.protobuf:protobuf-java”庫,版本 3.29 及更高。

ProtobufJsonDecoderProtobufJsonEncoder 變體支援讀寫 JSON 文件到 Protobuf 訊息。它們需要“com.google.protobuf:protobuf-java-util”依賴項。請注意,JSON 變體不支援讀取訊息流,有關更多詳細資訊,請參閱 ProtobufJsonDecoder 的 Javadoc

Google Gson

應用程式可以使用 GsonEncoderGsonDecoder,藉助 Google Gson 庫來序列化和反序列化 JSON 文件。此編解碼器支援 JSON 媒體型別和用於流的 NDJSON 格式。

Gson 不支援非阻塞解析,因此 GsonDecoder 不支援反序列化為 Flux<*> 型別。例如,如果此解碼器用於反序列化 JSON 流甚至作為 Flux<*> 的元素列表,則會在執行時丟擲 UnsupportedOperationException。應用程式應該轉而專注於反序列化有界集合,並使用 Mono<List<*>> 作為目標型別。

限制

緩衝部分或全部輸入流的 DecoderHttpMessageReader 實現可以配置記憶體中最大緩衝位元組數的限制。在某些情況下,緩衝發生是因為輸入被聚合並表示為單個物件——例如,帶有 @RequestBody byte[] 的控制器方法、x-www-form-urlencoded 資料等。緩衝也可能在流式傳輸中發生,當拆分輸入流時——例如,分隔文字、JSON 物件流等。對於這些流式傳輸情況,限制適用於流中與一個物件關聯的位元組數。

要配置緩衝區大小,您可以檢查給定的 DecoderHttpMessageReader 是否公開了 maxInMemorySize 屬性,如果公開,其 Javadoc 將詳細說明預設值。在伺服器端,ServerCodecConfigurer 提供了一個設定所有編解碼器的單一位置,請參見 HTTP 訊息編解碼器。在客戶端,所有編解碼器的限制可以在 WebClient.Builder 中更改。

對於多部分解析maxInMemorySize 屬性限制非檔案部分的大小。對於檔案部分,它決定了部分寫入磁碟的閾值。對於寫入磁碟的檔案部分,還有一個額外的 maxDiskUsagePerPart 屬性來限制每個部分的磁碟空間量。還有一個 maxParts 屬性來限制多部分請求中的總部分數。要在 WebFlux 中配置所有這三個,您需要向 ServerCodecConfigurer 提供一個預配置的 MultipartHttpMessageReader 例項。

當流式傳輸到 HTTP 響應時(例如,text/event-streamapplication/x-ndjson),定期傳送資料很重要,以便更快更可靠地檢測到斷開連線的客戶端。這種傳送可以是隻包含註釋的空 SSE 事件或任何其他“無操作”資料,這些資料實際上可以用作心跳。

DataBuffer

DataBuffer 是 WebFlux 中位元組緩衝區的表示。此參考的 Spring Core 部分在 資料緩衝區和編解碼器 部分對此有更多介紹。要理解的關鍵點是,在某些伺服器(如 Netty)上,位元組緩衝區是池化的並具有引用計數,並且在消耗後必須釋放以避免記憶體洩漏。

WebFlux 應用程式通常不需要擔心這些問題,除非它們直接消耗或生成資料緩衝區,而不是依賴編解碼器轉換為高階物件,或者除非它們選擇建立自定義編解碼器。對於這種情況,請檢視 資料緩衝區和編解碼器 中的資訊,特別是 使用 DataBuffer 部分。

日誌

Spring WebFlux 中的 DEBUG 級別日誌記錄被設計為緊湊、最小且人性化。它側重於反覆有用的高價值資訊位,而不是隻在除錯特定問題時有用的資訊。

TRACE 級別日誌記錄通常遵循與 DEBUG 相同的原則(例如,它也不應該是資訊洪流),但可用於除錯任何問題。此外,一些日誌訊息在 TRACE 級別和 DEBUG 級別可能顯示不同的詳細程度。

良好的日誌記錄來自使用日誌的經驗。如果您發現任何不符合既定目標的內容,請告訴我們。

日誌 ID

在 WebFlux 中,單個請求可以在多個執行緒上執行,執行緒 ID 對於關聯屬於特定請求的日誌訊息沒有用。這就是為什麼 WebFlux 日誌訊息預設以請求特定的 ID 為字首。

在伺服器端,日誌 ID 儲存在 ServerWebExchange 屬性中(LOG_ID_ATTRIBUTE),而基於該 ID 的完整格式化字首可從 ServerWebExchange#getLogPrefix() 獲取。在 WebClient 端,日誌 ID 儲存在 ClientRequest 屬性中(LOG_ID_ATTRIBUTE),而完整格式化字首可從 ClientRequest#logPrefix() 獲取。

敏感資料

DEBUGTRACE 日誌記錄可以記錄敏感資訊。這就是為什麼表單引數和頭預設被掩蓋,並且您必須顯式啟用它們的完整日誌記錄。

以下示例展示瞭如何為伺服器端請求執行此操作

  • 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()
© . This site is unofficial and not affiliated with VMware.