Poprzedni wpis był jedynie wprowadzeniem do Spring Boot’a 2.0. Pojawiło się tam pojęcie WebFlux. Jest to element, który w sposób reaktywny pozwala współpracować z warstwą webową. Ponadto, funkcjonalość ta dostarcza nam nowy sposób tworzenia endpointów poprzez użycie Router Functions. Zapraszam do wpisu!
Router Functions
Jak pisałem we wstępie, w nowym Spring Boot’cie możemy tworzyć naszą część serwerową na dwa sposoby (aktualnie nie można ich mieszać w jednej aplikacji):
- “Po staremu” – korzystając z adnotacji
@Controller
i innych związanych z Web’em - “Po nowemu” – korzystając z programowania funkcyjnego przy użyciu Router Functions
Dziś przedstawię wam nowy sposób (stary wszyscy bardzo dobrze znamy). Zaczynamy od dodania zależności dla WebFlux’a:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
Tworzymy konfigurację @Configuration
dla @Bean
typu RouterFunction
:
@Bean public RouterFunction<ServerResponse> routes(ReactiveHandler handler) { return route(GET("/code-couple"), handler::get); }
Pod endpointem /code-couple
, na metodzie GET zostanie wywołana metoda z HandlerFunction
(u nas ReactiveHandler
), czyli klasy, która trzyma logikę endpointów. Kolejne mapowania możemy dodawać w łatwy sposób dopisując kolejne wywołania:
@Bean public RouterFunction<ServerResponse> routes(ReactiveHandler handler) { return route(GET("/code-couple"), handler::get) .andRoute(POST("/code-couple"), handler::post); }
Wszystkie przydatne metody, których możemy używać przy tworzeniu route
znajdują się w klasie RequestPredicate
:
RequestPredicate.method(HttpMethod)
– jaka metoda z Http nas interesujeRequestPredicate.path(String)
– ścieżka endpointuRequestPredicate.contentType
– typ treściRequestPredicate.GET(String)
– jest połączeniemRequestPredicate.method(HttpMethod)
iRequestPredicate.path(String)
- warunki logiczne – do łączenia predykatów
RequestPredicate.*
– więcej
Po utworzeniu interesujących nas Router Functions pora na stworzenie HandlerFunction,
czyli w naszym przypadku klasy ReactiveHandler
:
@Component class ReactiveHandler { Mono<ServerResponse> get(ServerRequest request) { Mono<String> slogan = Mono.just("CodeCouple roxx!"); return ServerResponse.ok() .contentType(APPLICATION_JSON) .body(slogan, String.class); } Mono<ServerResponse> post(ServerRequest request) { Mono<String> value = request.bodyToMono(String.class); //do something with value return ServerResponse.created(URI.create("/code-couple/1")).body(value, String.class); } }
Każda metoda w HandlerFunction
przyjmuje ServerRequest
, natomiast zwraca Mono<ServerResponse>
. Klasa ServerRequest
dostarcza informacje o aktualnym żądaniu. Możemy z niej uzyskać takie informacje jak nagłówki czy parametry żądania. W odpowiedzi musimy natomiast zwrócić ServerResponse
, który dostarcza przyjazne API do tworzenia odpowiedzi:
Mono<ServerResponse> get(ServerRequest request) { String path = request.path(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).build(); }
Podsumowanie
Bardzo lubiłem wykorzystywać mapowanie “po staremu”, jednakże ostatnio coraz częściej korzystam z Router Functions. Jak dla mnie krótkie mapowania sprawdzają się dużo lepiej z Router Functions niż z klasycznym @RequestMapping.
Jaka jest wasza opinia na ten temat? Mieliście już przyjemność pracować z Router Functions?
GitHub
Całość jak zawsze na GitHub’ie.