一 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 auto-configuring Spring MVC,而不是 WebFlux。選擇此行為是因為許多 Spring 開發人員将
spring-boot-starter-webflux
添加到他們的 Spring MVC application 以使用 reactive
spring-boot-starter-webflux
。您仍然可以通過将所選的 application 類型設定為
WebClient
來強制執行您的選擇。
SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)
Spring WebFlux Auto-configuration
Spring Boot 為 Spring WebFlux 提供 auto-configuration,适用于大多數 applications。
auto-configuration 在 Spring 的預設值之上添加以下 features:
- 為
和HttpMessageReader
執行個體配置編解碼器(描述為稍後在本文檔中)。HttpMessageWriter
- 支援提供靜态資源,包括對 WebJars 的支援(描述為稍後在本文檔中)。
如果你想保留 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 支援:
- FreeMarker
- Thymeleaf
- 胡子
當您使用其中一個模闆引擎和預設的 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 中顯示的訂單:
訂購 | |
| |
(Spring Security) | |
| |
二 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老師"}