1. Pengenalan
Spring WebFlux adalah kerangka web berfungsi baru yang dibina menggunakan prinsip reaktif.
Dalam tutorial ini, kita akan belajar bagaimana bekerja dengannya dalam praktik.
Kami akan meletakkan ini dari panduan sedia ada kami untuk Spring 5 WebFlux. Dalam panduan itu, kami membuat aplikasi REST reaktif sederhana menggunakan komponen berasaskan anotasi. Di sini, kami akan menggunakan kerangka berfungsi.
2. Ketergantungan Maven
Kami memerlukan pergantungan spring-boot-starter-webflux yang sama seperti yang ditentukan dalam artikel sebelumnya:
org.springframework.boot spring-boot-starter-webflux 2.2.6.RELEASE
3. Kerangka Web Berfungsi
Kerangka web fungsional memperkenalkan model pengaturcaraan baru di mana kami menggunakan fungsi untuk merutekan dan menangani permintaan.
Berbanding dengan model berasaskan anotasi di mana kita menggunakan pemetaan anotasi, di sini kita akan menggunakan HandlerFunction dan RouterFunction s.
Begitu juga, seperti pada pengawal anotasi, pendekatan titik akhir berfungsi dibina pada timbunan reaktif yang sama.
3.1. Fungsi Penangan
The HandlerFunction mewakili fungsi yang menjana jawapan untuk permintaan dihalakan kepada mereka:
@FunctionalInterface public interface HandlerFunction { Mono handle(ServerRequest request); }
Antara muka ini terutamanya Fungsi
Walaupun, dibandingkan dengan layanan Servlet # standar (ServletRequest req, ServletResponse res) , HandlerFunction tidak mengambil respons sebagai parameter input.
3.2. Fungsi Penghala
RouterFunction berfungsi sebagai alternatif kepada anotasi @RequestMapping . Kita dapat menggunakannya untuk merutekan permintaan ke fungsi pengendali:
@FunctionalInterface public interface RouterFunction { Mono
route(ServerRequest request); // ... }
Biasanya, kita dapat mengimport fungsi pembantu RouterFunctions.route () untuk membuat laluan, bukannya menulis fungsi penghala yang lengkap.
Ini membolehkan kita merutekan permintaan dengan menerapkan RequestPredicate. Apabila predikat dipadankan, maka argumen kedua, fungsi pengendali, dikembalikan:
public static RouterFunction route( RequestPredicate predicate, HandlerFunction handlerFunction)
Kerana kaedah route () mengembalikan RouterFunction , kita dapat mengaitkannya untuk membina skema routing yang kuat dan kompleks.
4. Aplikasi REST Reaktif Menggunakan Web Berfungsi
Dalam panduan sebelumnya, kami membuat aplikasi EmployeeManagement REST yang mudah menggunakan @RestController dan WebClient.
Sekarang, mari kita laksanakan logik yang sama menggunakan fungsi penghala dan pengendali.
Pertama, kita perlu mewujudkan laluan menggunakan RouterFunction untuk menerbitkan dan mengambil aliran reaktif kami pekerja s .
Laluan didaftarkan sebagai biji kacang dan boleh dibuat di dalam kelas konfigurasi mana pun.
4.1. Sumber Tunggal
Mari buat laluan pertama kami menggunakan RouterFunction yang menerbitkan satu sumber Kakitangan :
@Bean RouterFunction getEmployeeByIdRoute() { return route(GET("/employees/{id}"), req -> ok().body( employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)); }
Argumen pertama adalah predikat permintaan. Perhatikan bagaimana kita menggunakan kaedah RequestPredicates.GET yang diimport secara statik di sini. Parameter kedua menentukan fungsi pengendali yang akan digunakan jika predikat berlaku.
Dengan kata lain, contoh di atas mengarahkan semua permintaan GET untuk / pekerja / {id} ke kaedah EmployeeRepository # findEm EmployeeById (String id) .
4.2. Sumber Pengumpulan
Seterusnya, untuk menerbitkan sumber koleksi, mari tambahkan laluan lain:
@Bean RouterFunction getAllEmployeesRoute() { return route(GET("/employees"), req -> ok().body( employeeRepository().findAllEmployees(), Employee.class)); }
4.3. Kemas kini Sumber Tunggal
Akhir sekali, mari kita tambahkan laluan untuk mengemas kini sumber pekerja :
@Bean RouterFunction updateEmployeeRoute() { return route(POST("/employees/update"), req -> req.body(toMono(Employee.class)) .doOnNext(employeeRepository()::updateEmployee) .then(ok().build())); }
5. Menyusun Laluan
Kami juga dapat menyusun rute bersama dalam satu fungsi penghala.
Mari lihat bagaimana menggabungkan laluan yang dibuat di atas:
@Bean RouterFunction 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()))); }
Here, we've used RouterFunction.and() to combine our routes.
Finally, we've implemented the complete REST API needed for our EmployeeManagement application, using routers and handlers.
To run the application, we can either use separate routes or the single, composed one that we created above.
6. Testing Routes
We can use WebTestClient to test our routes.
To do so, we first need to bind the routes using the bindToRouterFunction method and then build the test client instance.
Let's test our 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); }
and similarly getAllEmployeesRoute:
@Test public void whenGetAllEmployees_thenCorrectEmployees() { WebTestClient client = WebTestClient .bindToRouterFunction(config.getAllEmployeesRoute()) .build(); List employees = Arrays.asList( new Employee("1", "Employee 1"), new Employee("2", "Employee 2")); Flux employeeFlux = Flux.fromIterable(employees); given(employeeRepository.findAllEmployees()).willReturn(employeeFlux); client.get() .uri("/employees") .exchange() .expectStatus() .isOk() .expectBodyList(Employee.class) .isEqualTo(employees); }
We can also test our updateEmployeeRoute by asserting that our Employee instance is updated via EmployeeRepository:
@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); }
For more details on testing with WebTestClient please refer to our tutorial on working with WebClient and WebTestClient.
7. Summary
In this tutorial, we introduced the new functional web framework in Spring 5 and looked into its two core interfaces – RouterFunction and HandlerFunction. We also learned how to create various routes to handle the request and send the response.
Selain itu, kami membuat semula aplikasi EmployeeManagement yang diperkenalkan dalam panduan Spring 5 WebFlux dengan model titik akhir yang berfungsi.
Seperti biasa, kod sumber lengkap boleh didapati di Github.