Spring 5中的功能性Web框架簡介

1.簡介

Spring WebFlux是使用反應式原理構建的新的功能性Web框架。

在本教程中,我們將學習如何在實踐中使用它。

我們將以現有的Spring 5 WebFlux指南為基礎。在該指南中,我們使用基於註解的組件創建了一個簡單的反應式REST應用程序。在這裡,我們將使用功能框架。

2. Maven依賴

我們將需要與上一篇文章中定義的相同的[spring-boot-starter-webflux](https://search.maven.org/classic#search%7Cga%7C1%7Ca%3A%22spring-boot-starter-webflux%22%20AND%20g%3A%22org.springframework.boot%22)依賴項:

<dependency>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-starter-webflux</artifactId>

 <version>2.2.6.RELEASE</version>

 </dependency>

3.功能性Web框架

功能性Web框架引入了一種新的編程模型,其中我們使用功能來路由和處理請求。

而不是在這裡我們使用註釋映射基於註解模型,在這裡我們將使用HandlerFunctionRouterFunction

類似地,就像在帶註釋的控制器中一樣,功能端點方法建立在相同的reactive堆上。

3.1. HandlerFunction

HandlerFunction表示一個函數,該函數為路由到它們的請求生成響應:

@FunctionalInterface

 public interface HandlerFunction<T extends ServerResponse> {

 Mono<T> handle(ServerRequest request);

 }

該接口主要是Function<Request, Response<T>> ,其行為非常類似於servlet。

儘管與標準Servlet#service(ServletRequest req, ServletResponse res)HandlerFunction並不將響應作為輸入參數。

3.2。 RouterFunction

RouterFunction可以替代@RequestMapping註釋。我們可以使用它將請求路由到處理程序函數:

@FunctionalInterface

 public interface RouterFunction<T extends ServerResponse> {

 Mono<HandlerFunction<T>> route(ServerRequest request);

 // ...

 }

通常,我們可以導入輔助函數RouterFunctions.route()來創建路由,而不是編寫完整的路由器功能。

它允許我們通過應用RequestPredicate.路由請求RequestPredicate.當謂詞匹配時,則返回第二個參數,即處理程序函數:

public static <T extends ServerResponse> RouterFunction<T> route(

 RequestPredicate predicate,

 HandlerFunction<T> handlerFunction)

因為route()方法返回一個RouterFunction ,所以我們可以將其鏈接起來以構建強大而復雜的路由方案。

4.使用功能性Web的反應性REST應用程序

在之前的指南中,我們使用@RestControllerWebClient.創建了一個簡單的EmployeeManagement REST應用程序WebClient.

現在,讓我們使用路由器和處理程序功能實現相同的邏輯。

首先,我們需要使用RouterFunction創建路由以發布和使用Employee的響應流.

路由被註冊為Spring Bean,可以在任何配置類中創建。

4.1。單一資源

讓我們使用發佈單個Employee資源的RouterFunction創建第一個路由:

@Bean

 RouterFunction<ServerResponse> getEmployeeByIdRoute() {

 return route(GET("/employees/{id}"),

 req -> ok().body(

 employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class));

 }

第一個參數是請求謂詞。請注意,我們在此處如何使用靜態導入的RequestPredicates.GET方法。第二個參數定義一個處理程序函數,如果謂詞適用,將使用該函數。

換句話說,上面的示例將/employees/{id}所有GET請求路由到EmployeeRepository#findEmployeeById(String id)方法。

4.2。集合資源

接下來,要發布集合資源,讓我們添加另一條路線:

@Bean

 RouterFunction<ServerResponse> getAllEmployeesRoute() {

 return route(GET("/employees"),

 req -> ok().body(

 employeeRepository().findAllEmployees(), Employee.class));

 }

4.3。單一資源更新

最後,讓我們添加一條更新Employee資源的途徑:

@Bean

 RouterFunction<ServerResponse> updateEmployeeRoute() {

 return route(POST("/employees/update"),

 req -> req.body(toMono(Employee.class))

 .doOnNext(employeeRepository()::updateEmployee)

 .then(ok().build()));

 }

5.組成路由

我們還可以在單個路由器功能中將路由組合在一起。

讓我們看看如何合併上面創建的路由:

@Bean

 RouterFunction<ServerResponse> composedRoutes() {

 return

 route(GET("/employees"),

 req -> ok().body(

 employeeRepository().findAllEmployees(), Employee.class))



 .and(route(GET("/employees/{id}"),

 req -> ok().body(

 employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)))



 .and(route(POST("/employees/update"),

 req -> req.body(toMono(Employee.class))

 .doOnNext(employeeRepository()::updateEmployee)

 .then(ok().build())));

 }

在這裡,我們使用了RouterFunction.and()來組合路由。

最後,我們已經使用路由器和處理程序實現了EmployeeManagement應用程序所需的完整REST API。

為了運行該應用程序,我們可以使用單獨的路由,也可以使用上面創建的單個路由。

6.測試路由

我們可以使用WebTestClient來測試我們的路線。

為此,我們首先需要使用bindToRouterFunction方法綁定路由,然後構建測試客戶端實例。

讓我們測試一下getEmployeeByIdRoute

@Test

 public void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() {

 WebTestClient client = WebTestClient

 .bindToRouterFunction(config.getEmployeeByIdRoute())

 .build();



 Employee employee = new Employee("1", "Employee 1");



 given(employeeRepository.findEmployeeById("1")).willReturn(Mono.just(employee));



 client.get()

 .uri("/employees/1")

 .exchange()

 .expectStatus()

 .isOk()

 .expectBody(Employee.class)

 .isEqualTo(employee);

 }

和類似的getAllEmployeesRoute

@Test

 public void whenGetAllEmployees_thenCorrectEmployees() {

 WebTestClient client = WebTestClient

 .bindToRouterFunction(config.getAllEmployeesRoute())

 .build();



 List<Employee> employees = Arrays.asList(

 new Employee("1", "Employee 1"),

 new Employee("2", "Employee 2"));



 Flux<Employee> employeeFlux = Flux.fromIterable(employees);

 given(employeeRepository.findAllEmployees()).willReturn(employeeFlux);



 client.get()

 .uri("/employees")

 .exchange()

 .expectStatus()

 .isOk()

 .expectBodyList(Employee.class)

 .isEqualTo(employees);

 }

我們還可以通過斷言Employee實例通過EmployeeRepository更新來測試updateEmployeeRoute

@Test

 public void whenUpdateEmployee_thenEmployeeUpdated() {

 WebTestClient client = WebTestClient

 .bindToRouterFunction(config.updateEmployeeRoute())

 .build();



 Employee employee = new Employee("1", "Employee 1 Updated");



 client.post()

 .uri("/employees/update")

 .body(Mono.just(employee), Employee.class)

 .exchange()

 .expectStatus()

 .isOk();



 verify(employeeRepository).updateEmployee(employee);

 }

有關使用WebTestClient進行測試的更多詳細信息,請參閱我們的有關使用WebClientWebTestClient教程。

7.總結

在本教程中,我們在Spring 5中介紹了新的功能性Web框架,並研究了其兩個核心接口– RouterFunctionHandlerFunction.我們還學習瞭如何創建各種路由來處理請求和發送響應。

此外,我們使用功能端點模型重新創建了Spring 5 WebFlux指南中引入的EmployeeManagement應用程序。

與往常一樣,完整的源代碼可以在Github上找到。