從一開始學習 Netty 到 rxjava、Rector,再到 java8 的 CompletableFuture,就深深的爲響應式編程着迷,這種區別於傳統的順序式編程,沒準將來能在編程世界開闢一片天地呢!html
而後接觸到了 WebFlux 框架,也是充滿了濃厚的興趣,想好好琢磨一番,奈何中文資料實在太少,就打起了英文文檔的主意,惋惜英文水平實在捉急,老是看下一句,忘了上一句。誒,要不咱一句句翻譯出來吧,這樣讀起來就通順了,順即可以造福下後來學習者(想着翻譯的東西要被人看,也是一份堅持的動力)。java
翻譯並無逐字逐句去糾結,力求語義通順,有理解錯誤的地方,還麻煩你們指出,一塊兒學習探討。另外,文中還補充了一些本身練習的 demo。react
原文連接:https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/htmlsingle/#boot-features-webfluxgit
github 練習 demo:https://github.com/JMCuixy/webfluxgithub
tips:翻譯是一項提升英語和學習技能一箭雙鵰的事呀!web
Spring WebFlux 是 Spring 5.0 引入的新的響應式框架,區別於 Spring MVC,它不須要依賴Servlet API,它是徹底異步非阻塞的,而且基於 Reactor 來實現響應式流規範。spring
Spring WebFlux 有兩種表現形式:基於配置和基於註釋。基於註釋的實現方式很是相似於 SpringMVC 模型,如如下實例:apache
@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 框架的一部分,其參考文檔中提供了詳細信息。json
你能夠定義任意數量的 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)
Spring Boot 爲 Spring WebFlux 提供的自動配置基本能適用於大多數應用。
Spring Boot 的提供的自動配置主要作了如下兩個工做:
若是你想要保持 Spring Boot WebFlux 的自動配置功能,而且想添加額外的 WebFlux 配置項,你能夠自定義 @Configuration 配置類,但不要添加 @EnableWebFlux 註解。
若是你想要徹底控制 WebFlux,你能夠定義@Configuration 配置類,而且添加 @EnableWebFlux. 註解。
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 -> { // ... } } }
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/** 。
tips:Spring WebFlux 應用程序不嚴格依賴 Servlet API,所以不能將它們部署爲 war 文件,也不使用 src/main/webapp 目錄。
Spring WebFlux 除了提供 REST web 服務外,還支持渲染動態 HTML 內容,Spring WebFlux 支持一系列模板引擎,包括 Thymeleaf、FreeMarker 和 Mustache。
Spring Boot 爲如下的模板引擎提供了自動配置的支持:
當你使用了其中某個模板引擎,並選擇了 Spring Boot 自動配置,你須要將你的模板文件放在 src/main/resources/templates 目錄下,以便被 Spring Boot 發現。
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>
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 |