Введение в функциональную веб-платформу весной 5
1. Вступление
Фреймворк Spring WebFlux представляет новый функциональный веб-фреймворк, построенный с использованием реактивных принципов.
В этом руководстве мы узнаем, как работать с этим фреймворком на практике.
Мы будем основывать это на существующем руководствеGuide to Spring 5 WebFlux. В этом руководстве мы создали небольшое реактивное REST-приложение с использованием компонентов на основе аннотаций. Здесь мы воспользуемся функциональной структурой.
2. Maven Dependency
Нам понадобится та же зависимостьspring-boot-starter-webflux, как определено в предыдущей статье:
org.springframework.boot
spring-boot-starter-webflux
2.0.3.RELEASE
3. Функциональная веб-платформа
Функциональная веб-платформа представляет новую модель программирования, в которой мы используем функции для маршрутизации и обработки запросов.
В отличие от модели на основе аннотаций, в которой мы используем сопоставления аннотаций, здесь мы будем использоватьHandlerFunction иRouterFunctions.
Кроме того, функциональная веб-структура построена на том же реактивном стеке, на котором была построена основанная на аннотациях реактивная структура.
3.1. HandlerFunction
HandlerFunction представляет функцию, которая генерирует ответы на запросы, направленные к ним:
@FunctionalInterface
public interface HandlerFunction {
Mono handle(ServerRequest request);
}
Этот интерфейс в первую очередь представляет собойFunction<Request, Response<T>>, который ведет себя очень похоже на сервлет.
Хотя по сравнению со стандартным сервлетомServlet.service(ServletRequest req, ServletResponse res),HandlerFunction возвращает ответ вместо того, чтобы принимать его в качестве параметра, что делает его свободным от побочных эффектов и упрощает его тестирование и повторное использование.
3.2. RouterFunctionс
RouterFunction служит альтернативой аннотации@RequestMapping. Он используется для маршрутизации входящих запросов к функциям обработчика:
@FunctionalInterface
public interface RouterFunction {
Mono> route(ServerRequest request);
// ...
}
Обычно мы можем импортироватьRouterFunctions.route(), вспомогательную функцию для создания маршрутов вместо того, чтобы писать полную функцию маршрутизатора.
Это позволяет нам маршрутизировать запросы, применяяRequestPredicate.. Когда предикат совпадает, возвращается второй аргумент, функция-обработчик:
public static RouterFunction route(
RequestPredicate predicate,
HandlerFunction handlerFunction)
ВозвращаяRouterFunction,route() можно объединить в цепочку и вложить для построения мощных и сложных схем маршрутизации.
4. Реактивное приложение REST с использованием функциональной сети
В нашемguide to Spring WebFlux tutorial мы создаем небольшое приложениеEmployeeManagement REST, используя аннотированный@RestController andWebClient.
Теперь давайте создадим то же приложение, используя функцииRouter иHandler.
Для начала,we’ll create routes using RouterFunction to publish and consume our reactive streams of Employee. Routes зарегистрированы как bean-компоненты Spring и могут быть созданы внутри любого класса, который будет использоваться в качестве конфигурации Spring.
4.1. Единый ресурс
Давайте создадим наш первый маршрут, используяRouterFunction t, который публикует единственный ресурсEmployee:
@Bean
RouterFunction getEmployeeByIdRoute() {
return route(GET("/employees/{id}"),
req -> ok().body(
employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class));
}
Чтобы разбить его, первый аргумент определяет HTTP-запрос GET, который будет вызыватьhandler. I.e. вернуть совпавшийEmployee изemployeeRepository только, если путь равен/employee/{id} и запрос имеет тип GET.
4.2. Коллекционный ресурс
Затем для публикации ресурса коллекции добавим еще один маршрут:
@Bean
RouterFunction getAllEmployeesRoute() {
return route(GET("/employees"),
req -> ok().body(
employeeRepository().findAllEmployees(), Employee.class));
}
4.3. Обновление одного ресурса
Наконец, давайте добавим маршрут для обновленияEmployee resource:
@Bean
RouterFunction updateEmployeeRoute() {
return route(POST("/employees/update"),
req -> req.body(toMono(Employee.class))
.doOnNext(employeeRepository()::updateEmployee)
.then(ok().build()));
}
5. Составление маршрутов
Мы также можем составлять маршруты вместе в одной функции маршрутизатора.
Давайте объединим наши маршруты, созданные выше:
@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())));
}
Здесь мы использовалиRouterFunction.and() to для объединения наших маршрутов.
Наконец, мы создали все REST API, используяRouter иHandler t, которые нам нужны для нашего приложенияEmployeeManagement . Для запуска нашего приложения мы можем использовать разные маршруты или один составной, который мы создали выше.
6. Тестирование маршрутов
Мы можем использоватьWebTestClient для тестирования наших маршрутов.
Чтобы протестировать наши маршруты с помощьюWebTestClient we, необходимо связать наши маршруты с помощьюbindToRouterFunction and build нашего тестового экземпляра клиента.
Давайте проверим нашgetEmployeeByIdRoute:
@Test
public void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() {
WebTestClient client = WebTestClient
.bindToRouterFunction(config.getEmployeeByIdRoute())
.build();
Employee expected = 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(expected);
}
и аналогичноgetAllEmployeesRoute:
@Test
public void whenGetAllEmployees_thenCorrectEmployees() {
WebTestClient client = WebTestClient
.bindToRouterFunction(config.getAllEmployeesRoute())
.build();
List employeeList = new ArrayList<>();
Employee employee1 = new Employee("1", "Employee 1");
Employee employee2 = new Employee("2", "Employee 2");
employeeList.add(employee1);
employeeList.add(employee2);
Flux employeeFlux = Flux.fromIterable(employeeList);
given(employeeRepository.findAllEmployees()).willReturn(employeeFlux);
client.get()
.uri("/employees")
.exchange()
.expectStatus()
.isOk()
.expectBodyList(Employee.class)
.isEqualTo(employeeList);
}
Мы также можем проверить нашupdateEmployeeRoute, заявив, что обновленный синстансEmployee обновляется через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);
}
Для получения дополнительных сведений о тестировании с помощьюWebTestClient обратитесь к нашему руководству поworking with WebClient and WebTestClient.
7. Резюме
В этом руководстве мы представили новый функциональный веб-фреймворк в Spring 5, мы изучили его две основные функцииRouter andHandler и узнали, как создавать различные маршруты для обработки запроса и отправки ответа.
Кроме того, мы воссоздали наше приложениеEmployeeManagement , представленное вGuide to Spring 5 WebFlux, с нашей новой структурой.
Основываясь наReactor, реактивный фреймворк полностью обеспечит реактивный доступ к хранилищам данных. К сожалению, большинство хранилищ данных пока не предоставляют такой реактивный доступ, за исключением нескольких баз данныхNoSQL, таких какMongoDB.
Как всегда, полный исходный код можно найтиover on Github.