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框架引入了一種新的編程模型,其中我們使用功能來路由和處理請求。
而不是在這裡我們使用註釋映射基於註解模型,在這裡我們將使用HandlerFunction
和RouterFunction
類似地,就像在帶註釋的控制器中一樣,功能端點方法建立在相同的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應用程序
在之前的指南中,我們使用@RestController
和WebClient.
創建了一個簡單的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
進行測試的更多詳細信息,請參閱我們的有關使用WebClient
和WebTestClient
教程。
7.總結
在本教程中,我們在Spring 5中介紹了新的功能性Web框架,並研究了其兩個核心接口– RouterFunction
和HandlerFunction.
我們還學習瞭如何創建各種路由來處理請求和發送響應。
此外,我們使用功能端點模型重新創建了Spring 5 WebFlux指南中引入的EmployeeManagement
應用程序。
與往常一樣,完整的源代碼可以在Github上找到。