一 Spring WebFlux Framework說明
Spring WebFlux 是 Spring Framework 5.0 中引入的新 reactive web framework。與 Spring MVC 不同,它不需要 Servlet API,完全異步和 non-blocking,並通過反應堆項目實現Reactive Streams規范。
Spring WebFlux 有兩種版本:功能和 annotation-based。 annotation-based 一個非常接近 Spring MVC model,如下面的示例所示:
@RestController @RequestMapping("/users") public class MyRestController { @GetMapping("/{user}") public Mono<User> getUser(@PathVariable Long user) { // ... } @GetMapping("/{user}/customers") public Flux<Customer> getUserCustomers(@PathVariable Long user) { // ... } @DeleteMapping("/{user}") public Mono<User> deleteUser(@PathVariable Long user) { // ... } }
函數變量“WebFlux.fn”將路由配置與請求的實際處理分開,如下面的示例所示:
@Configuration public class RoutingConfiguration { @Bean public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) { return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser) .andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers) .andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser); } }
@Component public class UserHandler { public Mono<ServerResponse> getUser(ServerRequest request) { // ... } public Mono<ServerResponse> getUserCustomers(ServerRequest request) { // ... } public Mono<ServerResponse> deleteUser(ServerRequest request) { // ... } }
WebFlux 是 Spring Framework 的一部分,其詳細信息可在reference 文檔中找到。
您可以根據需要定義盡可能多的
RouterFunction
beans 來模塊化 router 的定義。如果需要應用優先級,可以訂購 Beans。
要開始,請將spring-boot-starter-webflux
模塊添加到 application。
在 application 中添加
spring-boot-starter-web
和spring-boot-starter-webflux
模塊會導致 Spring Boot auto-configuring Spring MVC,而不是 WebFlux。選擇此行為是因為許多 Spring 開發人員將spring-boot-starter-webflux
添加到他們的 Spring MVC application 以使用 reactiveWebClient
。您仍然可以通過將所選的 application 類型設置為SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)
來強制執行您的選擇。
Spring WebFlux Auto-configuration
Spring Boot 為 Spring WebFlux 提供 auto-configuration,適用於大多數 applications。
auto-configuration 在 Spring 的默認值之上添加以下 features:
如果你想保留 Spring Boot WebFlux features 並且想要添加額外的WebFlux configuration,你可以添加自己的@Configuration
class 類型為WebFluxConfigurer
但而不是 @EnableWebFlux
。
如果要完全控制 Spring WebFlux,可以添加自己的@Configuration
注釋@EnableWebFlux
。
帶有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 編解碼器
Spring WebFlux 使用HttpMessageReader
和HttpMessageWriter
接口來轉換 HTTP 請求和響應。通過查看 classpath 中可用的 libraries,它們配置為CodecConfigurer
以具有合理的默認值。
Spring Boot 通過使用CodecCustomizer
實例進一步自定義。例如,spring.jackson.*
configuration 鍵應用於 Jackson 編解碼器。
如果需要添加或自定義編解碼器,可以創建自定義CodecCustomizer
component,如下面的示例所示:
import org.springframework.boot.web.codec.CodecCustomizer; @Configuration public class MyConfiguration { @Bean public CodecCustomizer myCodecCustomizer() { return codecConfigurer -> { // ... } } }
你也可以利用Boot 的自定義 JSON 序列化器和反序列化器。
靜態內容
默認情況下,Spring Boot 為 classpath 中名為/static
(或/public
或/resources
或/META-INF/resources
)的目錄提供靜態內容。它使用來自 Spring WebFlux 的ResourceWebHandler
,以便您可以通過添加自己的WebFluxConfigurer
並覆蓋addResourceHandlers
方法來修改該行為。
默認情況下,資源映射到/**
,但您可以通過設置spring.webflux.static-path-pattern
property 來調整它。例如,將所有資源重新定位到/resources/**
可以實現如下:
spring.webflux.static-path-pattern=/resources/**
您還可以使用spring.resources.static-locations
自定義靜態資源位置。這樣做會將默認值替換為目錄位置列表。如果這樣做,默認的歡迎頁面檢測將切換到您的自定義位置。因此,如果您在啟動時的任何位置都有index.html
,那么它就是 application 的主頁。
除了前面列出的“標准”靜態資源位置之外,還為Webjars 內容做了一個特例。如果 jar files 包含在 Webjars 格式中,則中包含路徑的所有資源都將從 jar files 提供。
Spring WebFlux applications 並不嚴格依賴於 Servlet API,因此不能將它們部署為 war files 並且不要使用
src/main/webapp
目錄。
模板引擎
與 REST web services 一樣,您也可以使用 Spring WebFlux 來提供動態 HTML 內容。 Spring WebFlux 支持各種模板技術,包括 Thymeleaf,FreeMarker 和 Mustache。
Spring Boot 包括對以下模板引擎的 auto-configuration 支持:
當您使用其中一個模板引擎和默認的 configuration 時,您的模板將從src/main/resources/templates
自動獲取。
錯誤處理
Spring Boot 提供WebExceptionHandler
,以合理的方式處理所有錯誤。它在處理 order 中的位置緊接在 WebFlux 提供的處理程序之前,這被認為是最后的。對於機器客戶端,它會生成一個 JSON 響應,其中包含錯誤,HTTP 狀態和 exception 消息的詳細信息。對於瀏覽器客戶端,有一個“whitelabel”錯誤處理程序,它以 HTML 格式呈現相同的數據。您還可以提供自己的 HTML 模板來顯示錯誤(請參閱下一節)。
自定義此 feature 的第一個步驟通常涉及使用現有機制,但替換或擴充錯誤內容。為此,您可以添加ErrorAttributes
類型的 bean。
要更改錯誤處理行為,可以實現ErrorWebExceptionHandler
並注冊該類型的 bean 定義。因為WebExceptionHandler
非常 low-level,所以 Spring Boot 還提供了一個方便的AbstractErrorWebExceptionHandler
來讓你以 WebFlux 的方式處理錯誤,如下面的例子所示:
public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { // Define constructor here @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions .route(aPredicate, aHandler) .andRoute(anotherPredicate, anotherHandler); } }
要獲得更完整的圖片,您還可以直接子類化DefaultErrorWebExceptionHandler
並覆蓋特定方法。
自定義錯誤頁面
如果要為給定狀態 code 顯示自定義 HTML 錯誤頁面,可以將文件添加到/error
文件夾。錯誤頁面可以是靜態 HTML(即,添加到任何靜態資源文件夾下)或使用模板構建。文件的 name 應該是確切的狀態 code 或系列掩碼。
例如,要 map 404
到靜態 HTML 文件,您的文件夾結構如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
要使用 Mustache 模板 map 所有5xx
錯誤,您的文件夾結構如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates>
Web 過濾器
Spring WebFlux 提供了一個WebFilter
接口,可以實現過濾 HTTP request-response 交換。在 application context 中找到的WebFilter
beans 將自動用於過濾每個交換。
如果過濾器的 order 很重要,則可以實現Ordered
或使用@Order
進行注釋。 Spring Boot auto-configuration 可以為您配置 web 過濾器。執行此操作時,將使用以下 table 中顯示的訂單:
Web 過濾器 | 訂購 |
---|---|
MetricsWebFilter |
Ordered.HIGHEST_PRECEDENCE + 1 |
WebFilterChainProxy (Spring Security) |
-100 |
HttpTraceWebFilter |
Ordered.LOWEST_PRECEDENCE - 10 |
二 WebClient
Spring Boot 將 auto-detect 用於驅動WebClient
,具體取決於 application classpath 上可用的 libraries。目前,支持 Reactor Netty 和 Jetty RS client。
spring-boot-starter-webflux
starter 默認依賴於io.projectreactor.netty:reactor-netty
,它帶來了 server 和 client implementations。如果您選擇使用 Jetty 作為 reactive 服務器,則應該在 Jetty Reactive HTTP client library,org.eclipse.jetty:jetty-reactive-httpclient
上添加依賴項。對服務器和 client 使用相同的技術具有優勢,因為它將自動在 client 和服務器之間共享 HTTP 資源。
開發人員可以通過提供自定義ReactorResourceFactory
或JettyResourceFactory
bean 來覆蓋 Jetty 和 Reactor Netty 的資源 configuration - 這將應用於 clients 和服務器。
如果您希望覆蓋 client 的該選項,您可以定義自己的ClientHttpConnector
bean 並完全控制 client configuration。
您可以了解有關Spring Framework reference 文檔中的 WebClient configuration 選項的更多信息。
WebClient 自定義
WebClient
自定義有三種主要方法,具體取決於您希望自定義應用的廣泛程度。
要使任何自定義的范圍盡可能窄,請 inject auto-configured WebClient.Builder
然后根據需要調用其方法。 WebClient.Builder
實例是有狀態的:構建器上的任何更改都會反映在隨后使用它創建的所有 client 中。如果要使用相同的構建器創建多個 client,還可以考慮使用WebClient.Builder other = builder.clone();
克隆構建器。
要對所有WebClient.Builder
實例進行 application-wide 添加自定義,可以聲明WebClientCustomizer
beans 並在注入點本地更改WebClient.Builder
。
最后,您可以回退到原始 API 並使用WebClient.create()
。在這種情況下,不應用 auto-configuration 或WebClientCustomizer
三 代碼演示
-
pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> </dependencies>
注:webflux不能和web共存,webflux啟動的是netty。
-
public class User { private String id; private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User(String id, String name) { super(); this.id = id; this.name = name; } }
-
Service層
@Service public class UserService { private static final Map<String, User> dataMap = new HashMap<>(); static{ dataMap.put("1", new User("1", "小X老師")); dataMap.put("2", new User("2", "小D老師")); dataMap.put("3", new User("3", "小C老師")); dataMap.put("4", new User("4", "小L老師")); dataMap.put("5", new User("5", "小A老師")); dataMap.put("6", new User("6", "小S老師")); dataMap.put("7", new User("7", "小S老師")); } /** * 功能描述:返回用戶列表 * @return */ public Flux<User> list(){ Collection<User> list = UserService.dataMap.values(); return Flux.fromIterable(list); } /** * 功能描述:根據id查找用戶 * @param id * @return */ public Mono<User> getById(final String id){ return Mono.justOrEmpty(UserService.dataMap.get(id)); } /** * 功能描述:根據id刪除用戶 * @param id * @return */ public Mono<User> del(final String id){ return Mono.justOrEmpty(UserService.dataMap.remove(id)); } }
-
web層
@RestController @RequestMapping("/api/v1/user") public class UserController { //@Autowired //private UserService userService; private final UserService userService; public UserController(final UserService userService) { this.userService = userService; } @GetMapping("/test") public Mono<String> test(){ return Mono.just("hello"); } /** * 功能描述:根據id找用戶 * @param id * @return */ @GetMapping("find") public Mono<User> findByid(final String id){ return userService.getById(id); } /** * 功能描述:刪除用戶 * @param id * @return */ @GetMapping("del") public Mono<User> del(final String id){ return userService.del(id); } /** * 功能描述:列表 * @return */ @GetMapping(value="list",produces=MediaType.APPLICATION_STREAM_JSON_VALUE) public Flux<User> list(){ return userService.list().delayElements(Duration.ofSeconds(2)); } }
-
測試類
//@RunWith(SpringRunner.class) //@SpringBootTest public class WebfluxApplicationTests { @Test public void testBase(){ Mono<String> bodyMono = WebClient.create().get() .uri("http://localhost:8080/api/v1/user/find?id=1") .accept(MediaType.APPLICATION_JSON) .retrieve().bodyToMono(String.class); System.out.println(bodyMono.block()); } @Test public void testBase2(){ Mono<String> bodyMono = WebClient.create().get() .uri("http://localhost:8080/api/v1/user/find?id={id}",2) .accept(MediaType.APPLICATION_JSON) .retrieve().bodyToMono(String.class); System.out.println(bodyMono.block()); } } 注:着重講一下這個list方法,這個方法里面延遲2秒,會體現出是流一樣,每隔2秒出現一條數據。 {"id":"1","name":"小X老師"} {"id":"2","name":"小D老師"} {"id":"3","name":"小C老師"} {"id":"4","name":"小L老師"} {"id":"5","name":"小A老師"} {"id":"6","name":"小S老師"} {"id":"7","name":"小S老師"}