Pengenalan Rangka Kerja Web Berfungsi pada Musim Bunga 5

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 , yang berkelakuan seperti servlet.

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.