Opanowanie Spring Framework 5, Część 2: Spring WebFlux

Spring WebFlux wprowadza reaktywne tworzenie stron internetowych do ekosystemu Spring. Z tego artykułu dowiesz się, jak zacząć korzystać z systemów reaktywnych i programowania reaktywnego w Spring. Najpierw dowiesz się, dlaczego systemy reaktywne są ważne i jak są wdrażane w Spring Framework 5, a następnie uzyskasz praktyczne wprowadzenie do tworzenia usług reaktywnych za pomocą Spring WebFlux. Zbudujemy naszą pierwszą reaktywną aplikację przy użyciu adnotacji. Pokażę Ci również, jak zbudować podobną aplikację, korzystając z nowszych funkcji funkcjonalnych Springa.

Wiosenne samouczki na JavaWorld

Jeśli nie znasz jeszcze frameworka Spring, polecam zacząć od jednego z wcześniejszych samouczków z tej serii:

  • Co to jest wiosna? Programowanie oparte na komponentach dla języka Java
  • Opanowanie Spring Framework 5: Spring MVC

Systemy reaktywne i Spring WebFlux

Termin reaktywny jest obecnie popularny wśród programistów i menedżerów IT, ale zauważyłem niepewność co do tego, co tak naprawdę oznacza. Aby lepiej zrozumieć, czym są systemy reaktywne, warto zrozumieć podstawowy problem, który mają rozwiązać. W tej sekcji omówimy ogólnie systemy reaktywne i przedstawię interfejs API reaktywnych strumieni dla aplikacji Java.

Skalowalność w Spring MVC

Spring MVC zasłużył sobie na miejsce wśród najlepszych wyborów do tworzenia aplikacji internetowych i usług internetowych w języku Java. Jak odkryliśmy w Mastering Spring Framework 5, Część 1, Spring MVC płynnie integruje adnotacje z solidną architekturą aplikacji Spring. Umożliwia to programistom znającym Spring szybkie tworzenie satysfakcjonujących, wysoce funkcjonalnych aplikacji internetowych. Jednak skalowalność jest wyzwaniem dla aplikacji Spring MVC. To jest problem, który Spring WebFlux stara się rozwiązać.

Platformy blokujące a nieblokujące

W tradycyjnych aplikacjach internetowych, gdy serwer sieciowy otrzymuje żądanie od klienta, akceptuje je i umieszcza w kolejce wykonawczej. Wątek w puli wątków kolejki wykonawczej następnie odbiera żądanie, odczytuje jego parametry wejściowe i generuje odpowiedź. Po drodze, jeśli wątek wykonawczy musi wywołać zasób blokujący - taki jak baza danych, system plików lub inna usługa internetowa - ten wątek wykonuje żądanie blokowania i oczekuje na odpowiedź. W tym paradygmacie wątek jest skutecznie blokowany do czasu odpowiedzi zasobu zewnętrznego, co powoduje problemy z wydajnością i ogranicza skalowalność. Aby rozwiązać te problemy, programiści tworzą pule wątków o dużych rozmiarach, dzięki czemu gdy jeden wątek jest zablokowany, inny wątek może nadal przetwarzać żądania. Rysunek 1 przedstawia przepływ wykonywania tradycyjnej, blokującej aplikacji internetowej.

Steven Haines

Nieblokujące frameworki internetowe, takie jak NodeJS i Play, przyjmują inne podejście. Zamiast wykonywać żądanie blokujące i czekać na jego zakończenie, używają nieblokujących operacji we / wy. W tym paradygmacie aplikacja wykonuje żądanie, dostarcza kod do wykonania po zwróceniu odpowiedzi, a następnie zwraca swój wątek z powrotem do serwera. Gdy zasób zewnętrzny zwróci odpowiedź, podany kod zostanie wykonany. Wewnętrznie platformy nieblokujące działają przy użyciu pętli zdarzeń. W pętli kod aplikacji zapewnia wywołanie zwrotne lub przyszłość zawierającą kod do wykonania po zakończeniu pętli asynchronicznej.

Struktury nieblokujące są z natury sterowane zdarzeniami . Wymaga to innego paradygmatu programowania i nowego podejścia do rozumowania, w jaki sposób Twój kod zostanie wykonany. Kiedy już się nad tym zastanowisz, programowanie reaktywne może prowadzić do bardzo skalowalnych aplikacji.

Callback, obietnice i futures

Na początku JavaScript obsługiwał wszystkie funkcje asynchroniczne za pośrednictwem wywołań zwrotnych . W tym scenariuszu, gdy wystąpi zdarzenie (na przykład odpowiedź z wezwania serwisu) jest wykonywana. Chociaż wywołania zwrotne są nadal powszechne, asynchroniczna funkcjonalność JavaScript została ostatnio zmieniona w obietnice . W przypadku obietnic wywołanie funkcji wraca natychmiast, zwracając obietnicę dostarczenia wyników w przyszłości. Zamiast obietnic, Java implementuje podobny paradygmat wykorzystując futures . W tym zastosowaniu metoda zwraca przyszłość, która będzie miała wartość w pewnym momencie w przyszłości.

Programowanie reaktywne

Być może słyszałeś termin programowanie reaktywne w odniesieniu do frameworków i narzędzi do tworzenia stron internetowych, ale co to naprawdę oznacza? Termin, jaki znamy, pochodzi z Manifestu Reaktywnego, który definiuje systemy reaktywne jako mające cztery podstawowe cechy:

  1. Systemy reaktywne są responsywne , co oznacza, że ​​reagują na czas, we wszystkich możliwych okolicznościach. Koncentrują się na zapewnieniu szybkich i spójnych czasów reakcji, ustanawiając niezawodne górne granice, aby zapewnić stałą jakość usług.
  2. Systemy reaktywne są odporne , co oznacza, że ​​reagują w przypadku awarii. Odporność osiąga się za pomocą technik replikacji, powstrzymywania, izolacji i delegowania. Izolując komponenty aplikacji od siebie, można powstrzymać awarie i chronić system jako całość.
  3. Systemy reaktywne są elastyczne , co oznacza, że ​​reagują przy różnych obciążeniach. Osiąga się to poprzez elastyczne skalowanie komponentów aplikacji, aby sprostać aktualnemu zapotrzebowaniu.
  4. Systemy reaktywne są sterowane komunikatami , co oznacza, że ​​opierają się na asynchronicznym przekazywaniu komunikatów między komponentami. Pozwala to na tworzenie luźnych powiązań, izolacji i przejrzystości lokalizacji.

Rysunek 2 pokazuje, jak te cechy przepływają razem w systemie reaktywnym.

Steven Haines

Charakterystyka systemu reaktywnego

Systemy reaktywne są budowane poprzez tworzenie izolowanych komponentów, które komunikują się ze sobą asynchronicznie i mogą szybko skalować się, aby sprostać aktualnemu obciążeniu. Komponenty nadal zawodzą w systemach reaktywnych, ale w wyniku tej awarii są określone działania do wykonania, dzięki czemu system jako całość pozostaje funkcjonalny i responsywny.

Reaktywny manifest to streszczenie, ale aplikacje reaktywne zazwyczaj charakteryzuje się następującymi składnikami lub technik:

  • Strumienie danych : strumień to sekwencja zdarzeń uporządkowanych w czasie, takich jak interakcje użytkownika, wywołania usługi REST, komunikaty JMS i wyniki z bazy danych.
  • Asynchroniczne : zdarzenia strumienia danych są przechwytywane asynchronicznie, a kod definiuje, co zrobić, gdy zostanie wyemitowane zdarzenie, gdy wystąpi błąd i gdy strumień zdarzeń zostanie zakończony.
  • Nieblokujące : podczas przetwarzania zdarzeń kod nie powinien blokować ani wykonywać wywołań synchronicznych; zamiast tego powinien wykonywać wywołania asynchroniczne i odpowiadać, gdy ich wyniki są zwracane.
  • Przeciwciśnienie : komponenty sterują liczbą zdarzeń i częstotliwością ich emisji. W kategoriach reaktywnych Twój komponent jest nazywany subskrybentem, a zdarzenia są emitowane przez wydawcę . Jest to ważne, ponieważ abonent ma kontrolę nad tym, ile danych otrzymuje, a zatem nie będzie się przeciążać.
  • Komunikaty o błędach : zamiast składników generujących wyjątki, awarie są wysyłane jako komunikaty do funkcji obsługi. Podczas gdy zgłaszanie wyjątków przerywa strumień, zdefiniowanie funkcji obsługującej awarie w momencie ich wystąpienia nie.

Interfejs API reaktywnych strumieni

Nowe API Reactive Streams zostało stworzone między innymi przez inżynierów z Netflix, Pivotal, Lightbend, RedHat, Twitter i Oracle. Opublikowany w 2015 r. Interfejs Reactive Streams API jest teraz częścią języka Java 9. Definiuje cztery interfejsy:

  • Wydawca : emituje sekwencję zdarzeń do subskrybentów.
  • Subskrybent : odbiera i przetwarza zdarzenia emitowane przez wydawcę.
  • Subskrypcja : definiuje relację jeden do jednego między wydawcą a subskrybentem.
  • Procesor : reprezentuje etap przetwarzania składający się zarówno z abonenta, jak i wydawcy i przestrzega umów obu.

Rysunek 3 przedstawia relację między wydawcą, subskrybentem i subskrypcją.

Steven Haines

W istocie Abonent tworzy Subskrypcję do Wydawcy i gdy Wydawca ma dostępne dane, wysyła do Abonenta zdarzenie ze strumieniem elementów. Należy pamiętać, że subskrybent zarządza presją wsteczną w ramach swojej subskrypcji wydawcy.

Teraz, gdy wiesz już trochę o systemach reaktywnych i interfejsie API Reactive Streams, zwróćmy uwagę na narzędzia używane przez Spring do wdrażania systemów reaktywnych: Spring WebFlux i bibliotekę Reactor.

Projekt Reaktor

Project Reactor jest platformą zewnętrzną opartą na specyfikacji Java Reactive Streams, która służy do tworzenia nieblokujących aplikacji internetowych. Project Reactor udostępnia dwóch wydawców, które są intensywnie wykorzystywane w Spring WebFlux:

  • Mono : zwraca 0 lub 1 element.
  • Flux : zwraca 0 lub więcej elementów. Flux może być nieskończony, co oznacza, że ​​może emitować elementy w nieskończoność lub może zwrócić sekwencję elementów, a następnie wysłać powiadomienie o zakończeniu, gdy zwróci wszystkie swoje elementy.

Monosy i strumienie są koncepcyjnie podobne do futures, ale mają większą moc. Kiedy wywołujesz funkcję, która zwraca mono lub flux, zwróci ona natychmiast. Wyniki wywołania funkcji zostaną dostarczone w trybie mono lub flux, gdy staną się dostępne.

W Spring WebFlux będziesz wywoływać biblioteki reaktywne, które zwracają monos i fluxes, a twoje kontrolery zwracają monos i fluxes. Ponieważ zwracają się one natychmiast, kontrolery skutecznie zrezygnują z wątków i umożliwią Reactor asynchroniczne obsługiwanie odpowiedzi. Należy pamiętać, że tylko przy użyciu bibliotek reaktywnych usługi WebFlux mogą pozostać reaktywne. Jeśli używasz niereaktywnych bibliotek, takich jak wywołania JDBC, Twój kod będzie blokował i czekał na zakończenie tych wywołań przed powrotem.

Programowanie reaktywne z MongoDB

Obecnie nie ma wielu reaktywnych bibliotek baz danych, więc możesz się zastanawiać, czy pisanie usług reaktywnych jest praktyczne. Dobra wiadomość jest taka, że ​​MongoDB ma wsparcie reaktywne i istnieje kilka reaktywnych sterowników baz danych innych firm dla MySQL i Postgres. We wszystkich innych przypadkach WebFlux zapewnia mechanizm wykonywania wywołań JDBC w sposób reaktywny, aczkolwiek przy użyciu dodatkowej puli wątków, która blokuje wywołania JDBC.

Zacznij korzystać ze Spring WebFlux

Jako pierwszy przykład, utworzymy prostą usługę książki, która reaktywnie utrwala książki do iz MongoDB.

Zacznij od przejścia do strony głównej Spring Initializr, gdzie wybierzesz projekt Maven z Javą i wybierzesz najbardziej aktualną wersję Spring Boot (2.0.3 w momencie pisania tego tekstu). Nadaj projektowi nazwę grupy, na przykład „com.javaworld.webflux”, oraz nazwę artefaktu, na przykład „bookervice”. Rozwiń łącze Przełącz na pełną wersję, aby wyświetlić pełną listę zależności. Wybierz następujące zależności dla przykładowej aplikacji:

  • Sieć -> Sieć reaktywna : ta zależność obejmuje Spring WebFlux.
  • NoSQL -> Reactive MongoDB : Ta zależność obejmuje reaktywne sterowniki dla MongoDB.
  • NoSQL -> Embedded MongoDB : Ta zależność pozwala nam uruchomić osadzoną wersję MongoDB, więc nie ma potrzeby instalowania oddzielnej instancji. Zwykle jest to używane do testowania, ale włączymy to do naszego kodu wydania, aby uniknąć instalowania MongoDB.
  • Core -> Lombok : Korzystanie z Lombok jest opcjonalne, ponieważ nie potrzebujesz go do zbudowania aplikacji Spring WebFlux. Korzyści wynikające z zastosowania projektu Lombok jest to, że pozwala na dodawanie adnotacji do klas, które automatycznie wygeneruje pobierające i ustawiające, konstruktorów hashCode(), equals()i wiele innych.

Kiedy skończysz, powinieneś zobaczyć coś podobnego do rysunku 4.

Steven Haines

Naciśnięcie przycisku Generuj projekt spowoduje pobranie pliku ZIP zawierającego kod źródłowy projektu. Rozpakuj pobrany plik i otwórz go w swoim ulubionym IDE. Jeśli używasz IntelliJ, wybierz Plik, a następnie Otwórz i przejdź do katalogu, w którym pobrany plik zip został zdekompresowany.

Przekonasz się, że Spring Initializr wygenerował dwa ważne pliki:

  1. Plik Maven pom.xml, który zawiera wszystkie niezbędne zależności aplikacji.
  2. BookserviceApplication.java, która jest klasą startową Spring Boot dla aplikacji.

Listing 1 przedstawia zawartość wygenerowanego pliku pom.xml.