1. введение
Spring Web Flux-это новый функциональный веб-фреймворк, построенный с использованием реактивных принципов.
В этом уроке мы узнаем, как работать с ним на практике.
Мы будем основываться на нашем существующем руководстве по Spring 5 WebFlux . В этом руководстве мы создали простое реактивное приложение REST с использованием компонентов на основе аннотаций. Здесь вместо этого мы будем использовать функциональную структуру.
2. Зависимость Maven
Нам понадобится та же зависимость spring-boot-starter-web flux , что и в предыдущей статье:
org.springframework.boot spring-boot-starter-webflux 2.2.6.RELEASE
3. Функциональный Веб-Фреймворк
Функциональная веб-платформа вводит новую модель программирования, в которой мы используем функции для маршрутизации и обработки запросов.
В отличие от модели, основанной на аннотациях, где мы используем сопоставления аннотаций, здесь мы будем использовать функцию обработчика и функцию маршрутизатора s .
Аналогично, как и в аннотированных контроллерах, подход к функциональным конечным точкам построен на том же реактивном стеке.
3.1. Функция управления
Функция Обработчик представляет функцию, которая генерирует ответы на запросы, направляемые им:
@FunctionalInterface public interface HandlerFunction{ Mono handle(ServerRequest request); }
Этот интерфейс в первую очередь представляет собой функцию <Запрос, ответ> , которая ведет себя очень похоже на сервлет.
Хотя, по сравнению со стандартной службой Servlet#(ServletRequest req, ServletResponse res) , функция обработчика не принимает ответ в качестве входного параметра.
3.2. Функция маршрутизации
Функция маршрутизатора служит альтернативой аннотации @RequestMapping . Мы можем использовать его для маршрутизации запросов к функциям обработчика:
@FunctionalInterface public interface RouterFunction{ Mono > route(ServerRequest request); // ... }
Как правило, мы можем импортировать вспомогательную функцию Router Functions.router() для создания маршрутов вместо написания полной функции маршрутизатора.
Это позволяет нам направлять запросы, применяя предикат Request. Когда предикат сопоставлен, возвращается второй аргумент – функция обработчика:
public staticRouterFunction route( RequestPredicate predicate, HandlerFunction handlerFunction)
Поскольку метод route() возвращает функцию маршрутизатора , мы можем связать его в цепочку для построения мощных и сложных схем маршрутизации.
4. Реактивное Приложение REST С Использованием Функционального веб-Сайта
В нашем предыдущем руководстве мы создали простое приложение EmployeeManagement REST с использованием @RestController и WebClient.
Теперь давайте реализуем ту же логику, используя функции маршрутизатора и обработчика.
Во-первых, нам нужно создать маршруты с помощью функции маршрутизатора для публикации и использования наших реактивных потоков Сотрудников s .
Маршруты регистрируются как компоненты Spring и могут быть созданы в любом классе конфигурации.
4.1. Единый ресурс
Давайте создадим наш первый маршрут с помощью функции маршрутизатора , которая публикует один Сотрудник ресурс:
@Bean RouterFunctiongetEmployeeByIdRoute() { return route(GET("/employees/{id}"), req -> ok().body( employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)); }
Первый аргумент-это предикат запроса. Обратите внимание, как мы использовали статически импортированные предикаты запроса .Метод GET здесь. Второй параметр определяет функцию обработчика, которая будет использоваться, если применяется предикат.
Другими словами, приведенный выше пример направляет все запросы GET для /employees/{id} в EmployeeRepository#findEmployeeById(String id) метод.
4.2. Ресурс сбора
Далее, для публикации ресурса коллекции, давайте добавим еще один маршрут:
@Bean RouterFunctiongetAllEmployeesRoute() { return route(GET("/employees"), req -> ok().body( employeeRepository().findAllEmployees(), Employee.class)); }
4.3. Обновление Единого Ресурса
Наконец, давайте добавим маршрут для обновления ресурса Employee :
@Bean RouterFunctionupdateEmployeeRoute() { return route(POST("/employees/update"), req -> req.body(toMono(Employee.class)) .doOnNext(employeeRepository()::updateEmployee) .then(ok().build())); }
5. Составление Маршрутов
Мы также можем составить маршруты вместе в одной функции маршрутизатора.
Давайте посмотрим, как объединить маршруты, созданные выше:
@Bean RouterFunctioncomposedRoutes() { 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()))); }
Здесь мы использовали функцию Router.и() для объединения наших маршрутов.
Наконец, мы внедрили полный REST API, необходимый для нашего приложения Управление сотрудниками , используя маршрутизаторы и обработчики.
Для запуска приложения мы можем использовать либо отдельные маршруты, либо единый, составленный маршрут, который мы создали выше.
6. Маршруты тестирования
Мы можем использовать Клиент веб-тестирования чтобы проверить наши маршруты.
Для этого нам сначала нужно связать маршруты с помощью метода bind To Router Function , а затем построить экземпляр тестового клиента.
Давайте протестируем наш getEmployeeById маршрут :
@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); }
и аналогично getAllEmployees Route :
@Test public void whenGetAllEmployees_thenCorrectEmployees() { WebTestClient client = WebTestClient .bindToRouterFunction(config.getAllEmployeesRoute()) .build(); Listemployees = 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); }
Мы также можем протестировать наш обновить маршрут сотрудника , утверждая, что наш Экземпляр сотрудника обновляется через 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); }
Для получения более подробной информации о тестировании с помощью Web Test Client , пожалуйста, обратитесь к нашему учебнику по работе с WebClient и WebTestClient .
7. Резюме
В этом уроке мы представили новый функциональный веб – фреймворк весной 5 и рассмотрели его два основных интерфейса – RouterFunction и HandlerFunction. Мы также узнали, как создавать различные маршруты для обработки запроса и отправки ответа.
Кроме того, мы воссоздали наше приложение Управление сотрудниками , представленное в руководстве по Spring 5 WebFlux, с моделью функциональных конечных точек.
Как всегда, полный исходный код можно найти на Github .