Einführung in das Functional Web Framework im Frühjahr 5

1. Einleitung

Spring WebFlux ist ein neues funktionales Webframework, das nach reaktiven Prinzipien erstellt wurde.

In diesem Tutorial lernen wir, wie man in der Praxis damit arbeitet.

Wir stützen uns dabei auf unseren bestehenden Leitfaden zu Spring 5 WebFlux. In diesem Handbuch haben wir eine einfache reaktive REST-Anwendung mit annotationsbasierten Komponenten erstellt. Hier verwenden wir stattdessen den Funktionsrahmen.

2. Maven-Abhängigkeit

Wir benötigen dieselbe Spring-Boot-Starter-Webflux- Abhängigkeit wie im vorherigen Artikel definiert:

 org.springframework.boot spring-boot-starter-webflux 2.2.6.RELEASE 

3. Funktionales Web Framework

Das funktionale Webframework führt ein neues Programmiermodell ein, bei dem wir Funktionen zum Weiterleiten und Verarbeiten von Anforderungen verwenden.

Im Gegensatz zum annotationsbasierten Modell, bei dem wir Annotationszuordnungen verwenden, verwenden wir hier HandlerFunction und RouterFunction s.

Ähnlich wie bei den mit Anmerkungen versehenen Controllern basiert der Ansatz der funktionalen Endpunkte auf demselben reaktiven Stapel.

3.1. HandlerFunction

Die HandlerFunction stellt eine Funktion dar, die Antworten auf an sie weitergeleitete Anforderungen generiert:

@FunctionalInterface public interface HandlerFunction { Mono handle(ServerRequest request); }

Diese Schnittstelle ist in erster Linie eine Funktion , die sich sehr wie ein Servlet verhält.

Obwohl im Vergleich zu einem Standard - Servlet # service (ServletRequest req, ServletResponse res) , handler nimmt nicht eine Antwort als Eingabeparameter.

3.2. RouterFunktion

RouterFunction dient als Alternative zur Annotation @RequestMapping . Wir können es verwenden, um Anforderungen an die Handlerfunktionen weiterzuleiten:

@FunctionalInterface public interface RouterFunction { Mono
    
      route(ServerRequest request); // ... }
    

Normalerweise können wir die Hilfsfunktion RouterFunctions.route () importieren , um Routen zu erstellen, anstatt eine vollständige Routerfunktion zu schreiben.

Es ermöglicht uns, Anfragen durch Anwenden eines RequestPredicate weiterzuleiten. Wenn das Prädikat übereinstimmt, wird das zweite Argument, die Handlerfunktion, zurückgegeben:

public static  RouterFunction route( RequestPredicate predicate, HandlerFunction handlerFunction)

Da die route () -Methode eine RouterFunction zurückgibt , können wir sie verketten , um leistungsstarke und komplexe Routing-Schemata zu erstellen.

4. Reaktive REST-Anwendung mit Functional Web

In unserem vorherigen Handbuch haben wir eine einfache EmployeeManagement- REST-Anwendung mit @RestController und WebClient erstellt.

Lassen Sie uns nun dieselbe Logik mithilfe von Router- und Handlerfunktionen implementieren.

Erstens müssen wir Wege schaffen , mit RouterFunction unsere reaktiven Strom zu veröffentlichen und zu konsumieren Mitarbeiter s .

Routen werden als Spring Beans registriert und können in jeder Konfigurationsklasse erstellt werden.

4.1. Einzelne Ressource

Erstellen wir unsere erste Route mit RouterFunction , die eine einzelne Mitarbeiterressource veröffentlicht :

@Bean RouterFunction getEmployeeByIdRoute() { return route(GET("/employees/{id}"), req -> ok().body( employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)); }

The first argument is a request predicate. Notice how we used a statically imported RequestPredicates.GET method here. The second parameter defines a handler function that'll be used if the predicate applies.

In other words, the above example routes all the GET requests for /employees/{id} to EmployeeRepository#findEmployeeById(String id) method.

4.2. Collection Resource

Next, for publishing a collection resource, let's add another route:

@Bean RouterFunction getAllEmployeesRoute() { return route(GET("/employees"), req -> ok().body( employeeRepository().findAllEmployees(), Employee.class)); }

4.3. Single Resource Update

Lastly, let's add a route for updating the Employee resource:

@Bean RouterFunction updateEmployeeRoute() { return route(POST("/employees/update"), req -> req.body(toMono(Employee.class)) .doOnNext(employeeRepository()::updateEmployee) .then(ok().build())); }

5. Composing Routes

We can also compose the routes together in a single router function.

Let's see how to combine the routes created above:

@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.

Darüber hinaus haben wir unsere EmployeeManagement- Anwendung, die im Handbuch zu Spring 5 WebFlux eingeführt wurde, mit dem Modell für funktionale Endpunkte neu erstellt.

Wie immer finden Sie den vollständigen Quellcode auf Github.