Spring in Action Covers Spring 5-1--11 — страница 58 из 63

   extends ReactiveCrudRepository {

}

Однако наиболее важно отметить, что помимо работы с Flux вместо Iterable,, а также того, как вы получаете этот Flux, программная модель для определения реактивного контроллера WebFlux ничем не отличается от нереактивного контроллера Spring MVC. Оба аннотируются с помощью @RestController и высокоуровневого @RequestMapping на уровне класса. И оба имеют функции обработки запросов, которые аннотируются с помощью @GetMapping на уровне метода. Разница только в том, какой тип возвращают методы обработчика.

Еще одно важное замечание заключается в том, что, хотя вы получаете Flux из репозитория, вы можете вернуть его без вызова subscribe(). Действительно, фреймворк вызовет для вас метод subscribe(). Это означает, что при обработке запроса /design/recent будет вызван метод recentTacos(), который вернется до того, как данные будут даже получены из базы данных!

ВОЗВРАЩЕНИЕ ОДИНОЧНЫХ ЗНАЧЕНИЙ

В качестве другого примера рассмотрим метод tacoById() из DesignTacoController, как он был написан в главе 6:

@GetMapping("/{id}")

public Taco tacoById(@PathVariable("id") Long id) {

   Optional optTaco = tacoRepo.findById(id);

   if (optTaco.isPresent()) {

      return optTaco.get();

   }

   return null;

}

Здесь этот метод обрабатывает запросы GET для /design/{id} и возвращает одиночный объект Taco. Поскольку findById() репозитория возвращает Optional, вам пришлось написать какой-то неуклюжий код, чтобы справиться с этим. Но предположим на минуту, что findById() возвращает Mono вместо Optional. В этом случае вы можете переписать tacoById(), чтобы выглядело следующим образом:

@GetMapping("/{id}")

public Mono tacoById(@PathVariable("id") Long id) {

   return tacoRepo.findById(id);

}

Вау! Это намного проще. Однако более важно то, что, возвращая Mono вместо Taco, вы позволяете Spring WebFlux обрабатывать ответ реагирующим образом. Следовательно, ваш API будет лучше масштабироваться в ответ на большие нагрузки.

РАБОТА С ТИПАМИ RXJAVA

Стоит отметить, что хотя типы Reactor, такие как Flux и Mono, являются естественным выбором при работе с Spring WebFlux, вы также можете выбрать работу с типами RxJava, такими как Observable и Single. Например, предположим, что между DesignTacoController и внутренним репозиторием находится служба, которая работает в терминах типов RxJava. В этом случае метод recentTacos() может быть написан так:

@GetMapping("/recent")

public Observable recentTacos() {

   return tacoService.getRecentTacos();

}

Аналогично, метод tacoById() может быть написан для работы с RxJava Single, а не Mono:

@GetMapping("/{id}")

public Single tacoById(@PathVariable("id") Long id) {

   return tacoService.lookupTaco(id);

}

Кроме того, методы контроллера Spring WebFlux также могут возвращать RxJava Completable, который эквивалентен Mono в Reactor. WebFlux также может возвращать Flowable в качестве альтернативы Observable или Reactor Flux.

РЕАКТИВНАЯ ОБРАБОТКА ВХОДНЫХ ДАННЫХ

До сих пор мы интересовались только тем, какие реактивные типы возвращают методы контроллера. Но с Spring WebFlux вы также можете принять Mono или Flux в качестве входных данных для метода-обработчика. Для демонстрации рассмотрим оригинальную реализацию postTaco() из DesignTacoController:

@PostMapping(consumes="application/json")

@ResponseStatus(HttpStatus.CREATED)

public Taco postTaco(@RequestBody Taco taco) {

   return tacoRepo.save(taco);

}

postTaco() не только возвращает простой объект Taco, но также принимает объект Taco, связанный с содержимым в теле запроса. Это означает, что postTaco() не может быть вызван до тех пор, пока полезная нагрузка запроса не будет полностью подготовлена для создания экземпляра объекта Taco. Это означает, что postTaco() не может быть возвращен, пока не завершится блокирующий вызов метода save() репозитория. Другими словами, запрос блокируется дважды: при поступлении в postTaco() и снова внутри postTaco(). Но, применив небольшое реактивное кодирование к postTaco(), вы можете сделать его полностью неблокирующим методом обработки запросов:

@PostMapping(consumes="application/json")

@ResponseStatus(HttpStatus.CREATED)

public Mono postTaco(@RequestBody Mono tacoMono) {

   return tacoRepo.saveAll(tacoMono).next();

}

Здесь postTaco() принимает Mono и вызывает метод saveAll() хранилища, который, как вы увидите в следующей главе, принимает любую реализацию Reactive Streams Publisher, включая Mono или Flux. Метод saveAll() возвращает Flux, но поскольку вы работаете с Mono, вы знаете, что существует не более одного Taco, который будет опубликован Flux. Поэтому вы можете вызвать next(), чтобы получить Mono , который вернется из postTaco().

Принимая Mono в качестве входных данных, метод вызывается немедленно, не дожидаясь готовности Taco на основе тела запроса. И поскольку репозиторий также является реактивным, он примет Mono и немедленно вернет Flux, из которого вы вызываете next(), и вернете полученный Mono ... и все это еще до того, как запрос будет обработан!

Spring WebFlux является фантастической альтернативой Spring MVC, предлагая возможность написания реактивных веб-приложений с использованием той же модели разработки, что и Spring MVC. Но у Spring 5 есть еще одна новая хитрость. Давайте посмотрим, как создавать реагирующие API, используя новый функциональный стиль программирования Spring 5.

11.2 Определение функциональных обработчиков запросов

Модель программирования Spring MVC, основанная на аннотациях, существует начиная с Spring 2.5 и пользуется большой популярностью. Это имеет несколько недостатков.

Во-первых, любое программирование на основе аннотаций включает разделение в определении того, что аннотация должна делать и как она должна это делать. Сами аннотации определяют, что; а как это определено в другом месте в рамках кода. Это усложняет модель программирования, когда речь заходит о какой-либо настройке или расширении, поскольку такие изменения требуют работы в коде, внешнем по отношению к аннотации. Более того, отладка такого кода сложна, потому что вы не можете установить точку останова для аннотации.

Кроме того, поскольку популярность Spring продолжает расти, разработчики, впервые познакомившиеся с Spring в других языках и фреймворках, могут обнаружить что Spring MVC (и WebFlux) на основе аннотаций совсем не такой, как они уже его знают. В качестве альтернативы WebFlux Spring 5 представил новую модель функционального программирования для определения реактивных API.

Эта новая модель программирования используется больше как библиотека и меньше как фреймворк, позволяя сопоставлять запросы с кодом обработчика без аннотаций. Написание API с использованием модели функционального программирования Spring включает четыре основных типа:

-RequestPredicate — объявляет виды запросов, которые будут обработаны.

-RouterFunction - бъявляет, как соответствующий запрос должен быть направлен в код обработчика.

-ServerRequest - представляет собой HTTP-запрос, включая доступ к информации в header и body.

-ServerResponse - представляет ответ HTTP, включая информацию header и body

В качестве простого примера, который объединяет все эти типы, рассмотрим следующий пример Hello World:

package demo;

import static org.springframework.web.

        reactive.function.server.RequestPredicates.GET;

import static org.springframework.web.

        reactive.function.server.RouterFunctions.route;

import static org.springframework.web.

        reactive.function.server.ServerResponse.ok;

import static reactor.core.publisher.Mono.just;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.reactive.function.server.RouterFunction;

@Configuration

public class RouterFunctionConfig {

    @Bean

    public RouterFunction helloRouterFunction() {

        return route(GET("/hello"),

            request -> ok().body(just("Hello World!"), String.class));

    }

}

Первое, на что нужно обратить внимание, это то, что вы произвели статический импорт нескольких вспомогательных классов, которые вы можете использовать для создания вышеупомянутых функциональных типов. Вы также статически импортировали Mono, чтобы остальную часть кода было легче читать и понимать.

В этом классе аннотированном как @Configuration у вас есть один метод @Bean типа RouterFunction . Как уже упоминалось, RouterFunction объявляет сопоставления между одним или несколькими объектами RequestPredicate и функциями, которые будут обрабатывать соответствующие запрос(ы).

Метод route() из RouterFunctions принимает два параметра: RequestPredicate и функцию для обработки совпадающих запросов. В этом случае метод GET() из RequestPredicates объявляет RequestPredicate, который совпадает с HTTP-запросами GET для пути /hello.

Что касается функции-обработчика, она написана как лямбда, хотя она также может быть ссылкой на метод. Хотя это явно не объявлено, лямбда-обработчик принимает ServerRequest в качестве параметра. Он возвращает ServerResponse, используя ok() из ServerResponse и body() из BodyBuilder, который был возвращен из ok(). Это было сделано для того, чтобы создать ответ с кодом состояния HTTP 200 (ОК) и полезной нагрузкой body с надписью Hello World!

Метод helloRouterFunction() объявляет RouterFunction, которая обрабатывает только один вид запроса. Но если вам нужно обработать запрос другого типа, вам не нужно писать другой метод @Bean, хотя вы можете это сделать. Вам нужно только вызвать andRoute(), чтобы объявить другое сопоставление RequestPredicate-to-function. Например, вот как вы можете добавить другой обработчик для запросов GET для /bye: