1. 程式人生 > 其它 >Spring WebFlux 教程:如何構建反應式 Web 應用程式

Spring WebFlux 教程:如何構建反應式 Web 應用程式

Spring WebFlux 教程:如何構建反應式 Web 應用程式

反應式系統提供了我們在高資料流世界中所需的無與倫比的響應能力和可擴充套件性。然而,反應式系統需要經過專門培訓的工具和開發人員來實現這些獨特的程式架構。Spring WebFlux with Project Reactor 是一個專門為滿足現代公司的響應式需求而構建的框架。

今天,我們將通過解釋 WebFlux 如何與其他反應式堆疊工具配合、有何不同以及如何製作您的第一個應用程式來幫助您開始使用 WebFlux。

什麼是反應式系統?

反應式系統是採用反應式架構模式設計的系統,該模式優先使用鬆耦合、靈活和可擴充套件的元件。它們的設計還考慮了故障解決方案,以確保即使出現故障,大部分系統仍能執行。

反應式系統專注於:

  • 反應性:最重要的是,反應性系統應該對任何使用者輸入做出快速響應。反應式系統倡導者認為,反應式有助於優化系統的所有其他部分,從資料收集到使用者體驗。

  • 彈性:反應式系統的設計應該能夠預測系統故障。反應式系統期望元件最終會失效,並設計鬆散耦合的系統,即使幾個單獨的部件停止工作也能保持活動狀態。

  • 彈性:反應式系統應該通過擴大或縮小以滿足需求來適應工作負載的大小。許多反應式系統還將使用預測性擴充套件來預測和準備突然變化。實現彈性的關鍵是消除任何瓶頸並構建可以根據需要分片或複製元件的系統。

  • 訊息驅動的通訊:反應式系統的所有元件都是鬆散耦合的,每個元件之間都有硬邊界。您的系統應該通過顯式訊息傳遞跨越這些邊界進行通訊。這些訊息讓不同的元件瞭解故障,並幫助他們將工作流委派給可以處理它的元件。

反應式和其他 Web 模式之間最顯著的區別是反應式系統可以一次執行多個未阻塞的呼叫,而不是讓一些呼叫等待其他呼叫。因此,響應式系統可以提高效能和響應速度,因為 Web 應用程式的每個部分都可以比必須等待另一部分更快地完成自己的部分。

什麼是反應堆專案?

Project Reactor 是一個由 Pivotal 構建並由 Spring 提供支援的框架。它實現了反應式 API 模式,最著名的是反應式流規範。

如果您熟悉Java 8 Streams,您會很快發現 Stream 和 Flux(或其單元素版本 Mono)之間的許多相似之處。它們之間的主要區別在於 Fluxes 和 Monos 遵循一種publisher-subscriber模式並實現背壓,而 Stream API 則沒有。

背壓是資料端點向資料生產者發出訊號,表明它接收了太多資料的一種方式。這允許更好的流量管理和分配,因為它可以防止單個元件過度工作。

使用 Reactor 的主要優點是您可以完全控制資料流。您可以依靠訂閱者在準備好處理資訊時詢問更多資訊的能力,或者在釋出者端緩衝一些結果,甚至使用沒有背壓的全推送方法。

在我們的反應式堆疊中,它位於 Spring Boot 2.0 和 WebFlux 之上:

示例反應式堆疊

堆疊:技術堆疊是用於建立 Web 或移動應用程式的軟體產品和程式語言的組合。反應式堆疊是相同的,但用於建立反應式應用程式。

什麼是 Spring WebFlux?

Spring WebFlux 是一個完全非阻塞、基於註解的 Web 框架,它構建在 Project Reactor 之上,它使得在 HTTP 層上構建響應式應用程式成為可能。WebFlux 使用新的路由器功能特性將函數語言程式設計應用於 Web 層並繞過宣告性控制器和請求對映。WebFlux 要求您將 Reactor 作為核心依賴項匯入。

WebFlux 作為Spring MVC的響應式替代品在 Spring 5 中新增,並增加了對以下內容的支援:

  • 非阻塞執行緒:無需等待先前任務完成即可完成指定任務的併發執行緒。
  • Reactive Stream API:一種標準化工具,包括用於非阻塞背壓的非同步流處理選項。
  • 非同步資料處理:當資料在後臺處理並且使用者可以不間斷地繼續使用正常的應用程式功能時。

最終WebFlux摒棄了SpringMVC的多請求執行緒模型,而是使用多EventLoop非阻塞模型來啟用反應式、可擴充套件的應用程式。由於支援Netty、Undertow 和Servlet 3.1+ 容器等流行伺服器,WebFlux 已成為反應式堆疊的關鍵部分。

Router功能

RouterFunction是標準springmvc中使用的@RequestMapping和@Controller註釋樣式的一種功能替代。

我們可以使用它將請求路由到處理程式函式:

  • 傳統的路由定義
@RestController
publicclassProductController{
@RequestMapping("/product")
publicList<Product>productListing(){
returnps.findAll();
}
}
  • 函式式定義
@Bean
publicRouterFunction<ServerResponse>productListing(ProductServiceps){
returnroute().GET("/product",req->ok().body(ps.findAll()))
.build();
}

你可以使用RouterFunctions.route()來建立路由,而不是編寫完整的路由器函式。路由註冊為spring的bean,因此可以在任何配置類中建立。 路由器功能避免了由請求對映的多步驟過程引起的潛在副作用,而是將其簡化為直接的路由器/處理程式鏈。這允許函數語言程式設計實現反應式程式設計。

RequestMapping和Controller註釋樣式在WebFlux中仍然有效如果您對舊樣式更熟悉,RouterFunctions只是解決方案的一個新選項。

WebClient 詳解

專案中經常用到傳送Http請求的客戶端,如果你使用webflux那非常簡單去建立一個Http請求。WebClient是WebFlux的反應式web客戶端,它是從著名的rest模板構建的。它是一個介面,表示web請求的主要入口點,並支援同步和非同步操作。WebClient主要用於反應式後端到後端通訊。

您可以通過使用Maven匯入標準WebFlux依賴項來構建和建立WebClient例項:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

建立例項


WebClientwebClient=WebClient.create();
//如果是呼叫特定服務的API,可以在初始化webclient時使用,baseUrl
WebClientwebClient=WebClient.create("https://github.com/1ssqq1lxr");

或者構造器方式初始化

WebClientwebClient1=WebClient.builder()
.baseUrl("https://github.com/1ssqq1lxr")
.defaultHeader(HttpHeaders.CONTENT_TYPE,"application/vnd.github.v3+json")
.defaultHeader(HttpHeaders.USER_AGENT,"Spring5WebClient")
.build();
  • Get請求
Mono<String>resp=WebClient.create()
.method(HttpMethod.GET)
.uri("https://github.com/1ssqq1lxr")
.cookie("token","xxxx")
.header(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.retrieve().bodyToMono(String.class);
  • Post請求(表單)
MultiValueMap<String,String>formData=newLinkedMultiValueMap();
formData.add("name1","value1");
formData.add("name2","value2");
Mono<String>resp=WebClient.create().post()
.uri("http://www.w3school.com.cn/test/demo_form.asp")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.retrieve().bodyToMono(String.class);
  • Post請求(Body)
Bookbook=newBook();
book.setName("name");
book.setTitle("thisistitle");
Mono<String>resp=WebClient.create().post()
.uri("https://github.com/1ssqq1lxr")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(book),Book.class)
.retrieve().bodyToMono(String.class);
  • 檔案上傳
HttpHeadersheaders=newHttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
HttpEntity<ClassPathResource>entity=newHttpEntity<>(newClassPathResource("parallel.png"),headers);
MultiValueMap<String,Object>parts=newLinkedMultiValueMap<>();
arts.add("file",entity);
Mono<String>resp=WebClient.create().post()
.uri("http://localhost:8080/upload")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(parts))
.retrieve().bodyToMono(String.class);

Reactive Steam API

下篇文章給大家詳細講下Reactor3的API

Reactive Stream API是一個的函式集合,允許更智慧的流資料流。它內建了對背壓和非同步處理的支援,確保應用程式最有效地利用計算機和元件資源。

反應流API有四個主要介面:

  • Publisher:根據連結訂閱者的需求向他們釋出事件。充當訂戶可以監視事件的中心連結點。

  • Subscriber:接收和處理髮布伺服器發出的事件。多個訂閱伺服器可以連結到單個釋出伺服器,並對同一事件做出不同的響應。訂戶可以設定為反應:

    • onNext,當它接收到下一個事件時。

    • onSubscribe,新增新訂閱時

    • onError,當另一個訂閱伺服器發生錯誤時

    • onComplete,當一個訂閱完成時

Server容器

WebFlux在Tomcat、Jetty、servlet3.1+容器以及Netty和Undertow等非Servlet執行時上都受支援。Netty最常用於非同步和非阻塞設計,因此WebFlux將預設使用它。只需對Maven或Gradle構建軟體進行簡單的更改,就可以輕鬆地在這些伺服器選項之間切換。

這使得WebFlux在它可以使用的技術方面具有高度的通用性,並允許您使用現有的基礎設施輕鬆地實現它。

併發模型

WebFlux是以無阻塞的思想構建的,因此使用了與springmvc不同的併發程式設計模型。

springmvc假設執行緒將被阻塞,並在阻塞例項期間使用一個大的執行緒池來保持移動。這個更大的執行緒池使得MVC資源更密集,因為計算機硬體必須同時保持更多的線

WebFlux使用了一個小的執行緒池,因為它假設您永遠不需要通過工作來避免阻塞。這些執行緒稱為事件迴圈工作執行緒,數量固定,在傳入請求中的迴圈速度比MVC執行緒快。這意味著WebFlux更有效地使用計算機資源,因為活動執行緒總是在工作。

Spring WebFlux Security

WebFlux使用Spring安全性來實現身份驗證和授權協議。springsecurity使用WebFilter根據經過身份驗證的使用者列表認證請求。

@EnableWebFluxSecurity
publicclassHelloWebFluxSecurityConfig{
@Bean
publicMapReactiveUserDetailsServiceuserDetailsService(){
UserDetailsuser=User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
returnnewMapReactiveUserDetailsService(user);
}
}

在這裡,我們可以看到使用者有一個使用者名稱、一個密碼和一個或多個roles標籤,這些標籤允許自定義定訪問。類似於SpringBoot Security的 UserDetailsService介面

開始使用 Spring WebFlux

生成專案

spring程式碼生成器

參考配置

生成後的pom如下

<?xmlversion="1.0"encoding="UTF-8"?>
<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
<relativePath/><!--lookupparentfromrepository-->
</parent>
<groupId>com.github.webflux.learn</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>DemoprojectforSpringBoot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

開發介面

自定義一個函式路由:將請求path中的佔位引數獲取作為返回值

/**
*@authorcoding途中
*/
@Configuration
publicclassTestRouter{

@Bean
publicRouterFunction<ServerResponse>routeExample(){
returnRouterFunctions
.route(RequestPredicates.GET("/hello/{path}").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),serverRequest->{
Stringstr=serverRequest.pathVariable("path");
returnServerResponse.ok().contentType(MediaType.TEXT_PLAIN).bodyValue(str)
.switchIfEmpty(ServerResponse.notFound().build());
});
}
}

瀏覽器請求 http://localhost:4990/hello/haha

haha

新增認證


/**
*@authorcoding途中
*/

@Configuration
@EnableWebFluxSecurity
publicclassHelloWebfluxSecurityConfig{

@Bean
publicMapReactiveUserDetailsServiceuserDetailsService(){
UserDetailsuser=User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
returnnewMapReactiveUserDetailsService(user);
}

@Bean
publicSecurityWebFilterChainspringSecurityFilterChain(ServerHttpSecurityhttp){
//@formatter:off
returnhttp.authorizeExchange()
.pathMatchers("/hello/**").authenticated()
.pathMatchers("/hello/login").permitAll()
.anyExchange().authenticated()
.and()
.formLogin().and()
.logout().and()
.httpBasic().and()
.csrf().disable()
.build();
}
}
  • 再次請求介面 瀏覽器請求 http://localhost:4990/hello/haha 此時瀏覽重定向到 http://localhost:4990/login

登陸頁面

輸入user/user 使用者名稱密碼後完成登陸。

再次瀏覽器請求 http://localhost:4990/hello/authenticate

authenticate

結束

識別下方二維碼!回覆: 入群 ,掃碼加入我們交流群!

點贊是認可,在看是支援