Spring Boot WebFlux 2.1.7 中文翻譯文檔

  • 2019 年 11 月 3 日
  • 筆記

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/** 。

tips:Spring WebFlux 應用程式不嚴格依賴 Servlet API,因此不能將它們部署為 war 文件,也不使用 src/main/webapp 目錄。

6. 模板引擎

Spring WebFlux 除了提供 REST web 服務外,還支援渲染動態 HTML 內容,Spring WebFlux 支援一系列模板引擎,包括 Thymeleaf、FreeMarker 和 Mustache。

Spring Boot 為以下的模板引擎提供了自動配置的支援:

當你使用了其中某個模板引擎,並選擇了 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