反應式 Web 應用程式
Spring Boot 透過為 Spring Webflux 提供自動組態,簡化了反應式 Web 應用程式的開發。
“Spring WebFlux 框架”
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-web 和 spring-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 的預設值之上添加了以下功能
如果您想保留 Spring Boot WebFlux 功能,並想添加額外的 WebFlux 組態,您可以添加自己的 WebFluxConfigurer
類型的 @Configuration
類別,但不要使用 @EnableWebFlux
。
如果您想為自動組態的 HttpHandler
添加額外的自訂,您可以定義 WebHttpHandlerBuilderCustomizer
類型的 beans,並使用它們來修改 WebHttpHandlerBuilder
。
如果您想完全控制 Spring WebFlux,您可以添加自己的使用 @EnableWebFlux
註解的 @Configuration
。
Spring WebFlux 轉換服務
如果您想自訂 Spring WebFlux 使用的 ConversionService
,您可以提供一個帶有 addFormatters
方法的 WebFluxConfigurer
bean。
轉換也可以使用 spring.webflux.format.*
組態屬性進行自訂。如果未配置,則使用以下預設值
屬性 | DateTimeFormatter |
格式 |
---|---|---|
|
|
|
|
|
java.time 的 |
|
|
java.time 的 |
具有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 編解碼器
Spring WebFlux 使用 HttpMessageReader
和 HttpMessageWriter
介面來轉換 HTTP 請求和回應。它們透過 CodecConfigurer
進行配置,以透過查看類別路徑中可用的庫來獲得合理的預設值。
Spring Boot 為編解碼器提供了專用的組態屬性 spring.codec.*
。它還透過使用 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())
}
}
}
您還可以利用 Boot 的自訂 JSON 序列化器和反序列化器。
靜態內容
預設情況下,Spring Boot 從類別路徑中名為 /static
(或 /public
或 /resources
或 /META-INF/resources
)的目錄提供靜態內容。它使用 Spring WebFlux 中的 ResourceWebHandler
,以便您可以透過添加自己的 WebFluxConfigurer
並覆寫 addResourceHandlers
方法來修改該行為。
預設情況下,資源映射到 /**
,但您可以透過設定 spring.webflux.static-path-pattern
屬性來調整它。例如,將所有資源重新定位到 /resources/**
可以透過以下方式實現
-
屬性
-
YAML
spring.webflux.static-path-pattern=/resources/**
spring:
webflux:
static-path-pattern: "/resources/**"
您也可以使用 spring.web.resources.static-locations
自訂靜態資源位置。這樣做會將預設值替換為目錄位置列表。如果您這樣做,預設的歡迎頁面偵測將切換到您的自訂位置。因此,如果在您啟動時的任何位置都有 index.html
,則它將成為應用程式的首頁。
除了前面列出的“標準”靜態資源位置外,Webjars 內容還有一個特殊情況。Webjars content。預設情況下,如果任何路徑在 /webjars/**
中的資源以 Webjars 格式打包,則會從 jar 檔案中提供這些資源。路徑可以使用 spring.webflux.webjars-path-pattern
屬性進行自訂。
Spring WebFlux 應用程式並不嚴格依賴 Servlet API,因此它們不能部署為 war 檔案,也不使用 src/main/webapp 目錄。 |
歡迎頁面
Spring Boot 支援靜態和範本化歡迎頁面。它首先在配置的靜態內容位置中尋找 index.html
檔案。如果找不到,則尋找 index
範本。如果找到其中任何一個,它將自動用作應用程式的歡迎頁面。
這僅作為應用程式定義的實際索引路由的後備方案。排序由 HandlerMapping
beans 的順序定義,預設情況下為以下順序
|
使用 |
|
在 |
歡迎頁面的 |
歡迎頁面支援 |
範本引擎
除了 REST Web 服務外,您還可以將 Spring WebFlux 用於提供動態 HTML 內容。Spring WebFlux 支援多種範本技術,包括 Thymeleaf、FreeMarker 和 Mustache。
Spring Boot 包括對以下範本引擎的自動組態支援
當您使用這些範本引擎之一的預設組態時,您的範本會自動從 src/main/resources/templates
中選取。
錯誤處理
Spring Boot 提供了一個 WebExceptionHandler
,以合理的方式處理所有錯誤。它在處理順序中的位置緊接在 WebFlux 提供的處理程序之前,後者被認為是最後一個。對於機器客戶端,它會產生一個 JSON 回應,其中包含錯誤的詳細資訊、HTTP 狀態和例外訊息。對於瀏覽器客戶端,有一個“白標”錯誤處理程序,以 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 篩選器 | 順序 |
---|---|
|
|
|
|
嵌入式反應式伺服器支援
Spring Boot 包括對以下嵌入式反應式 Web 伺服器的支援:Reactor Netty、Tomcat、Jetty 和 Undertow。大多數開發人員使用適當的 starter 來取得完全配置的實例。預設情況下,嵌入式伺服器監聽 8080 端口上的 HTTP 請求。
自訂反應式伺服器
常見的反應式 Web 伺服器設定可以使用 Spring Environment
屬性進行配置。通常,您會在 application.properties
或 application.yaml
檔案中定義屬性。
常見的伺服器設定包括
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)
}
}
JettyReactiveWebServerFactory
、NettyReactiveWebServerFactory
、TomcatReactiveWebServerFactory
和 UndertowReactiveWebServerFactory
是 ConfigurableReactiveWebServerFactory
的專用變體,它們分別具有用於 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。如果您需要執行更特殊的操作,還提供了幾個受保護的方法“hooks”。有關詳細資訊,請參閱 ConfigurableReactiveWebServerFactory
API 文件。
自動組態的自訂程式仍然應用於您的自訂 factory,因此請謹慎使用該選項。 |
反應式伺服器資源組態
當自動組態 Reactor Netty 或 Jetty 伺服器時,Spring Boot 將建立特定的 beans,這些 beans 將為伺服器實例提供 HTTP 資源:ReactorResourceFactory
或 JettyResourceFactory
。
預設情況下,這些資源也將與 Reactor Netty 和 Jetty 客户端共享,以獲得最佳效能,前提是
-
伺服器和客户端使用相同的技術
-
客户端實例是使用 Spring Boot 自動組態的
WebClient.Builder
bean 建置的
開發人員可以透過提供自訂的 ReactorResourceFactory
或 JettyResourceFactory
bean 來覆寫 Jetty 和 Reactor Netty 的資源組態 - 這將應用於客户端和伺服器。
您可以在 WebClient Runtime 章節中了解更多關於客户端資源組態的資訊。