1. 前言
從一開始學習 Netty 到 rxjava、Rector,再到 java8 的 CompletableFuture,就深深的為響應式程式設計着迷,這種差別于傳統的順序式程式設計,沒準未來能在程式設計世界開辟一片天地呢!
然後接觸到了 WebFlux 架構,也是充滿了濃厚的興趣,想好好琢磨一番,奈何中文資料實在太少,就打起了英文文檔的主意,可惜英文水準實在捉急,總是看下一句,忘了上一句。诶,要不咱一句句翻譯出來吧,這樣讀起來就通順了,順便可以造福下後來學習者(想着翻譯的東西要被人看,也是一份堅持的動力)。
翻譯并沒有逐字逐句去糾結,力求語義通順,有了解錯誤的地方,還麻煩大家指出,一起學習探讨。另外,文中還補充了一些自己練習的 demo。
原文連結:https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/htmlsingle/#boot-features-webflux
github 練習 demo:https://github.com/JMCuixy/webflux
tips:翻譯是一項提高英語和學習技能一舉兩得的事呀!
2. WebFlux 簡介
Spring WebFlux 是 Spring 5.0 引入的新的響應式架構,差別于 Spring MVC,它不需要依賴Servlet API,它是完全異步非阻塞的,并且基于 Reactor 來實作響應式流規範。
Spring WebFlux 有兩種表現形式:基于配置和基于注釋。基于注釋的實作方式非常類似于 SpringMVC 模型,如以下執行個體:
@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) {
// ...
}
}
基于配置的實作方式,把路由和具體請求邏輯分離開,如以下執行個體:
@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 架構的一部分,其參考文檔中提供了詳細資訊。
你可以定義任意數量的 RouterFunction Bean,以對你的路由進行歸納整理。當然,你也可以針對多個 RouterFunction 設定優先級(@Order 注解)。
開始一個 WebFlux 項目,首先,需要将 spring-boot-starter-webflux 子產品引入你的項目。值得注意的是,如果你同時引入了 spring-boot-starter-web 和 spring-boot-starter-webflux 子產品會導緻 Spring Boot 自動配置Spring MVC,而不是 WebFlux。因為許多 Spring 開發人員引入 spring-boot-starter-webflux ,僅僅是為了使用它的響應式程式設計(這個理由也是絕了),當然你也可以強制把你的項目配置成 WebFlux:
SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)
3. 自動配置
Spring Boot 為 Spring WebFlux 提供的自動配置基本能适用于大多數應用。
Spring Boot 的提供的自動配置主要做了以下兩個工作:
- 為 HttpMessageReader 和 HttpMessageWriter 執行個體配置 HTTP 編解碼器
- 支援服務靜态資源映射,包括對 WebJars 資源的支援
如果你想要保持 Spring Boot WebFlux 的自動配置功能,并且想添加額外的 WebFlux 配置項,你可以自定義 @Configuration 配置類,但不要添加 @EnableWebFlux 注解。
如果你想要完全控制 WebFlux,你可以定義@Configuration 配置類,并且添加 @EnableWebFlux. 注解。
4. HttpMessageReaders 和 HttpMessageWriters 的 HTTP 編解碼器
Spring WebFlux 使用 HttpMessageReader 和 HttpMessageWriter 接口來轉換 HTTP 請求和響應,可以通過 CodecConfigurer 得到它們的預設配置:
public interface CodecConfigurer {
...
List<HttpMessageReader<?>> getReaders();
List<HttpMessageWriter<?>> getWriters();
...
}
Spring Boot 提供了 CodecCustomizer 接口,允許你進一步定制編解碼器,通過其 customize() 方法可以擷取到 CodecConfigurer 對象,進而可以注冊新的編解碼工具,或對現有的編解碼工具進行替換等。如以下執行個體:
import org.springframework.boot.web.codec.CodecCustomizer;
@Configuration
public class MyConfiguration {
@Bean
public CodecCustomizer myCodecCustomizer() {
return codecConfigurer -> {
// ...
}
}
}
5. 靜态資源
Spring Boot 預設從類路徑的以下目錄(/static、 /public 、/resources 、/META-INF/resources)加載靜态資源,當然,你可以自定義配置類實作 WebFluxConfigurer 并重寫 addResourceHandlers 方法來修改預設資源路徑:
@Configuration
public class MyWebFluxConfigurer implements WebFluxConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// do more
}
}
Spring Boot 預設将靜态資源映射在 /** 的路徑下,當然,你可以通過修改 spring.webflux.static-path-pattern 屬性來調整預設映射,例如,将所有資源映射到 /resources/** 路徑 ,可以通過以下方式實作:
spring.webflux.static-path-pattern=/resources/**
你也可以通過設定 spring.resources.static-locations 屬性值來自定義資源目錄,如果你這樣做了,預設的歡迎頁面檢測也将會切換到你設定的資源目錄。是以,在你的資源目錄中,隻要有一個 index.html 頁面,都将會成為你的應用首頁。
除了前面介紹的标準靜态資源外,還有一種特殊的情況,那就是 webjars 内容。如果靜态資源被打包成了 webjars 的格式,那麼通路這些資源的路徑就變成了 /webjars/** 。
Spring WebFlux 應用程式不嚴格依賴 Servlet API,是以不能将它們部署為 war 檔案,也不使用 src/main/webapp 目錄。
6. 模闆引擎
Spring WebFlux 除了提供 REST web 服務外,還支援渲染動态 HTML 内容,Spring WebFlux 支援一系列模闆引擎,包括 Thymeleaf、FreeMarker 和 Mustache。
Spring Boot 為以下的模闆引擎提供了自動配置的支援:
- FreeMarker
- Thymeleaf
- Mustache
當你使用了其中某個模闆引擎,并選擇了 Spring Boot 自動配置,你需要将你的模闆檔案放在 src/main/resources/templates 目錄下,以便被 Spring Boot 發現。
7. 異常處理
Spring Boot 提供了一個 WebExceptionHandler 用來處理所有錯誤,WebExceptionHandler 執行通常被認為是處理鍊中的最後一步,僅位于 WebFlux 提供服務之前。對于機器端,它通常是一個 JSON 響應,包含了HTTP 狀态碼、錯誤資訊等;對于浏覽器端,它通常是一個 “whitelabel” HTML 錯誤頁面,頁面渲染了相同的錯誤資訊。當然,你也可以提供自定義的 HTML 模闆來展示錯誤資訊(下文會說到)。
首先,定制此功能通常涉及利用現有機制,但要替換或增加錯誤内容,你可以添加 ErrorAttributes 類型的 Bean。
若要更改錯誤處理行為,可以實作 ErrorWebExceptionHandler 并注冊該類型的 bean 定義,但是 WebExceptionHandler 級别很低。是以 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);
}
}
如果你想要為給定的錯誤碼展示自定義的 HTML 錯誤頁面,你可以在 /error 目錄下添加一個錯誤頁面檔案。可以是靜态HTML(即添加到任意靜态資源檔案夾下),也可以使用模闆建構,檔案名應為确切的狀态碼或系列掩碼。
例如,要映射 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>
8. 過濾器
Spring WebFlux 提供了一個 WebFilter 接口,用來對 HTTP 請求-響應路由進行過濾,在應用程式上下文中找到的 WebFilter bean 将自動用于過濾每個路由!以下是一個簡單鑒權的過濾器 demo — 對于 沒有 token 參數的請求傳回 401 錯誤:
@Component
public class CustomWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> queryParams = request.getQueryParams();
if (queryParams == null || StringUtils.isEmpty(queryParams.getFirst("token"))) {
Map<String, String> resultMap = new HashMap<>();
resultMap.put("code", "401");
resultMap.put("msg", "非法請求");
byte[] datas = new byte[0];
try {
datas = new ObjectMapper().writeValueAsBytes(resultMap);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
ServerHttpResponse response = exchange.getResponse();
DataBuffer buffer = response.bufferFactory().wrap(datas);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
//Manipulate the response in some way
}));
}
}
可以通過實作 Ordered 接口或使用 @Order 注釋來設定過濾器的執行順序(執行順序是從小到大執行,較高的值被解釋為較低的優先級)。Spring Boot 的自動配置功能已經為你提供了一些内置的過濾器,如下是它們的執行順序:
Web Filter | Order |
---|---|
MetricsWebFilter | Ordered.HIGHEST_PRECEDENCE + 1 |
WebFilterChainProxy (Spring Security) | -100 |
HttpTraceWebFilter | Ordered.LOWEST_PRECEDENCE - 10 |