Mastering Spring Framework 5, Teil 2: Spring WebFlux

Spring WebFlux führt die reaktive Webentwicklung in das Spring-Ökosystem ein. In diesem Artikel werden Sie mit reaktiven Systemen und reaktiver Programmierung mit Spring vertraut gemacht. Zuerst erfahren Sie, warum reaktive Systeme wichtig sind und wie sie in Spring Framework 5 implementiert sind. Anschließend erhalten Sie eine praktische Einführung in die Erstellung reaktiver Dienste mit Spring WebFlux. Wir werden unsere erste reaktive Anwendung mithilfe von Anmerkungen erstellen. Ich werde Ihnen auch zeigen, wie Sie eine ähnliche Anwendung mit den neueren Funktionsfunktionen von Spring erstellen.

Frühlings-Tutorials auf JavaWorld

Wenn Sie mit dem Spring-Framework noch nicht vertraut sind, empfehle ich, mit einem der früheren Tutorials dieser Reihe zu beginnen:

  • Was ist Frühling? Komponentenbasierte Entwicklung für Java
  • Mastering Spring Framework 5: Spring MVC

Reaktive Systeme und Spring WebFlux

Der Begriff reaktiv ist derzeit bei Entwicklern und IT-Managern beliebt, aber ich habe einige Unsicherheiten darüber festgestellt, was er tatsächlich bedeutet. Um klarer zu werden, was reaktive Systeme sind, ist es hilfreich, das grundlegende Problem zu verstehen, das sie lösen sollen. In diesem Abschnitt werden wir uns allgemein mit reaktiven Systemen befassen und die Reactive Streams-API für Java-Anwendungen vorstellen.

Skalierbarkeit im Frühjahr MVC

Spring MVC hat sich seinen Platz unter den Top-Optionen für die Erstellung von Java-Webanwendungen und Webdiensten verdient. Wie wir in Mastering Spring Framework 5, Teil 1 festgestellt haben, integriert Spring MVC Anmerkungen nahtlos in die robuste Architektur einer Spring-basierten Anwendung. Auf diese Weise können mit Spring vertraute Entwickler schnell zufriedenstellende, hochfunktionale Webanwendungen erstellen. Die Skalierbarkeit ist jedoch eine Herausforderung für Spring MVC-Anwendungen. Dies ist das Problem, das Spring WebFlux angehen möchte.

Blockieren gegen nicht blockierende Webframeworks

Wenn in herkömmlichen Webanwendungen ein Webserver eine Anforderung von einem Client empfängt, akzeptiert er diese Anforderung und stellt sie in eine Ausführungswarteschlange. Ein Thread im Thread-Pool der Ausführungswarteschlange empfängt dann die Anforderung, liest seine Eingabeparameter und generiert eine Antwort. Wenn der Ausführungsthread eine blockierende Ressource aufrufen muss, z. B. eine Datenbank, ein Dateisystem oder einen anderen Webdienst, führt dieser Thread die Blockierungsanforderung aus und wartet auf eine Antwort. In diesem Paradigma wird der Thread effektiv blockiert, bis die externe Ressource antwortet, was zu Leistungsproblemen führt und die Skalierbarkeit einschränkt. Um diese Probleme zu bekämpfen, erstellen Entwickler Thread-Pools mit großzügiger Größe, sodass ein anderer Thread weiterhin Anforderungen verarbeiten kann, während ein Thread blockiert ist. Abbildung 1 zeigt den Ausführungsablauf für eine herkömmliche blockierende Webanwendung.

Steven Haines

Nicht blockierende Webframeworks wie NodeJS und Play verfolgen einen anderen Ansatz. Anstatt eine Blockierungsanforderung auszuführen und auf deren Abschluss zu warten, verwenden sie nicht blockierende E / A. In diesem Paradigma führt eine Anwendung eine Anforderung aus, stellt Code bereit, der ausgeführt werden soll, wenn eine Antwort zurückgegeben wird, und gibt dann ihren Thread an den Server zurück. Wenn eine externe Ressource eine Antwort zurückgibt, wird der bereitgestellte Code ausgeführt. Intern arbeiten nicht blockierende Frameworks mit einer Ereignisschleife. Innerhalb der Schleife stellt der Anwendungscode entweder einen Rückruf oder eine Zukunft bereit, die den Code enthält, der ausgeführt werden soll, wenn die asynchrone Schleife abgeschlossen ist.

Nicht blockierende Frameworks sind von Natur aus ereignisgesteuert . Dies erfordert ein anderes Programmierparadigma und einen neuen Ansatz, um zu überlegen, wie Ihr Code ausgeführt wird. Sobald Sie sich darum gekümmert haben, kann reaktive Programmierung zu sehr skalierbaren Anwendungen führen.

Rückrufe, Versprechen und Zukünfte

In der Anfangszeit handhabte JavaScript alle asynchronen Funktionen über Rückrufe . In diesem Szenario wird der Rückruf ausgeführt, wenn ein Ereignis auftritt (z. B. wenn eine Antwort von einem Serviceabruf verfügbar wird). Während Rückrufe immer noch weit verbreitet sind, hat sich die asynchrone Funktionalität von JavaScript in jüngerer Zeit zu Versprechungen entwickelt . Bei Versprechungen kehrt ein Funktionsaufruf sofort zurück und gibt ein Versprechen zurück, die Ergebnisse zu einem späteren Zeitpunkt zu liefern. Anstelle von Versprechungen implementiert Java ein ähnliches Paradigma mithilfe von Futures . In dieser Verwendung gibt eine Methode eine Zukunft zurück, die zu einem späteren Zeitpunkt einen Wert haben wird.

Reaktive Programmierung

Sie haben vielleicht den Begriff reaktive Programmierung für Webentwicklungs-Frameworks und -Tools gehört, aber was bedeutet das wirklich? Der Begriff, wie wir ihn kennengelernt haben, stammt aus dem Reactive Manifesto, das reaktive Systeme mit vier Kernmerkmalen definiert:

  1. Reaktive Systeme reagieren , dh sie reagieren unter allen möglichen Umständen zeitnah. Sie konzentrieren sich auf schnelle und konsistente Reaktionszeiten und legen zuverlässige Obergrenzen fest, um eine konsistente Servicequalität zu gewährleisten.
  2. Reaktive Systeme sind belastbar , was bedeutet, dass sie auch bei Ausfällen reaktionsfähig bleiben. Ausfallsicherheit wird durch die Techniken der Replikation, Eindämmung, Isolierung und Delegierung erreicht. Indem Sie Anwendungskomponenten voneinander isolieren, können Sie Fehler eindämmen und das gesamte System schützen.
  3. Reaktive Systeme sind elastisch , was bedeutet, dass sie bei unterschiedlichen Arbeitslasten reaktionsfähig bleiben. Dies wird erreicht, indem Anwendungskomponenten elastisch skaliert werden, um den aktuellen Bedarf zu decken.
  4. Reaktive Systeme sind nachrichtengesteuert , was bedeutet, dass sie auf der asynchronen Nachrichtenübertragung zwischen Komponenten beruhen. Auf diese Weise können Sie lose Kopplung, Isolation und Standorttransparenz erstellen.

Abbildung 2 zeigt, wie diese Merkmale in einem reaktiven System zusammenfließen.

Steven Haines

Eigenschaften eines reaktiven Systems

Reaktive Systeme werden erstellt, indem isolierte Komponenten erstellt werden, die asynchron miteinander kommunizieren und schnell skaliert werden können, um die aktuelle Last zu decken. Komponenten fallen in reaktiven Systemen immer noch aus, es gibt jedoch definierte Aktionen, die als Ergebnis dieses Fehlers ausgeführt werden müssen, wodurch das gesamte System funktionsfähig und reaktionsschnell bleibt.

Das reaktive Manifest ist abstrakt, aber reaktive Anwendungen sind typischerweise durch die folgenden Komponenten oder Techniken gekennzeichnet:

  • Datenströme : Ein Stream ist eine zeitlich geordnete Folge von Ereignissen, z. B. Benutzerinteraktionen, REST-Serviceaufrufe, JMS-Nachrichten und Ergebnisse aus einer Datenbank.
  • Asynchron : Datenstromereignisse werden asynchron erfasst und Ihr Code definiert, was zu tun ist, wenn ein Ereignis ausgegeben wird, wenn ein Fehler auftritt und wenn der Ereignisstrom abgeschlossen ist.
  • Nicht blockierend : Während Sie Ereignisse verarbeiten, sollte Ihr Code keine synchronen Aufrufe blockieren und ausführen. Stattdessen sollte es asynchrone Anrufe tätigen und antworten, wenn die Ergebnisse dieser Anrufe zurückgegeben werden.
  • Gegendruck : Komponenten steuern die Anzahl der Ereignisse und wie oft sie ausgegeben werden. In reaktiven Begriffen wird Ihre Komponente als Abonnent bezeichnet, und Ereignisse werden von einem Herausgeber ausgegeben . Dies ist wichtig, da der Teilnehmer die Kontrolle darüber hat, wie viele Daten er empfängt, und sich somit nicht selbst überlastet.
  • Fehlermeldungen : Anstelle von Komponenten, die Ausnahmen auslösen, werden Fehler als Meldungen an eine Handlerfunktion gesendet. Während das Auslösen von Ausnahmen den Stream unterbricht, funktioniert das Definieren einer Funktion zur Behandlung von auftretenden Fehlern nicht.

Die Reactive Streams API

Die neue Reactive Streams-API wurde unter anderem von Ingenieuren von Netflix, Pivotal, Lightbend, RedHat, Twitter und Oracle erstellt. Die 2015 veröffentlichte Reactive Streams-API ist jetzt Teil von Java 9. Sie definiert vier Schnittstellen:

  • Herausgeber : Gibt eine Folge von Ereignissen an Abonnenten aus.
  • Abonnent : Empfängt und verarbeitet von einem Publisher ausgegebene Ereignisse.
  • Abonnement : Definiert eine Eins-zu-Eins-Beziehung zwischen einem Publisher und einem Abonnenten.
  • Prozessor : Stellt eine Verarbeitungsstufe dar, die sowohl aus einem Abonnenten als auch einem Herausgeber besteht, und befolgt die Verträge beider.

Abbildung 3 zeigt die Beziehung zwischen einem Publisher, einem Abonnenten und einem Abonnement.

Steven Haines

Im Wesentlichen erstellt ein Abonnent ein Abonnement für einen Publisher und sendet, wenn der Publisher über verfügbare Daten verfügt, ein Ereignis mit einem Strom von Elementen an den Abonnenten. Beachten Sie, dass der Abonnent seinen Gegendruck innerhalb seines Abonnements für den Publisher verwaltet.

Nachdem Sie nun ein wenig über reaktive Systeme und die Reactive Streams-API wissen, wenden wir uns den Tools zu, die Spring zur Implementierung reaktiver Systeme verwendet: Spring WebFlux und die Reactor-Bibliothek.

Projektreaktor

Project Reactor ist ein Framework eines Drittanbieters, das auf der Reactive Streams-Spezifikation von Java basiert und zum Erstellen nicht blockierender Webanwendungen verwendet wird. Project Reactor bietet zwei Publisher, die in Spring WebFlux häufig verwendet werden:

  • Mono : Gibt 0 oder 1 Element zurück.
  • Fluss : Gibt 0 oder mehr Elemente zurück. Ein Fluss kann endlos sein, was bedeutet, dass er Elemente für immer ausgeben kann, oder er kann eine Folge von Elementen zurückgeben und dann eine Abschlussbenachrichtigung senden, wenn er alle seine Elemente zurückgegeben hat.

Monos und Flows ähneln konzeptionell Futures, sind jedoch leistungsfähiger. Wenn Sie eine Funktion aufrufen, die ein Mono oder einen Fluss zurückgibt, wird diese sofort zurückgegeben. Die Ergebnisse des Funktionsaufrufs werden Ihnen über Mono oder Flux übermittelt, sobald sie verfügbar sind.

In Spring WebFlux rufen Sie reaktive Bibliotheken auf, die Monos und Flows zurückgeben, und Ihre Controller geben Monos und Flows zurück. Da diese sofort zurückkehren, geben Ihre Controller ihre Threads effektiv auf und ermöglichen Reactor, Antworten asynchron zu verarbeiten. Es ist wichtig zu beachten, dass Ihre WebFlux-Dienste nur durch die Verwendung reaktiver Bibliotheken reaktiv bleiben können. Wenn Sie nicht reaktive Bibliotheken wie JDBC-Aufrufe verwenden, wird Ihr Code blockiert und wartet, bis diese Aufrufe abgeschlossen sind, bevor Sie zurückkehren.

Reaktive Programmierung mit MongoDB

Derzeit gibt es nicht viele reaktive Datenbankbibliotheken. Sie fragen sich daher möglicherweise, ob es sinnvoll ist, reaktive Dienste zu schreiben. Die gute Nachricht ist, dass MongoDB reaktive Unterstützung bietet und es einige reaktive Datenbanktreiber von Drittanbietern für MySQL und Postgres gibt. Für alle anderen Anwendungsfälle bietet WebFlux einen Mechanismus zum reaktiven Ausführen von JDBC-Aufrufen, obwohl ein sekundärer Thread-Pool verwendet wird, mit dem JDBC-Aufrufe blockiert werden.

Beginnen Sie mit Spring WebFlux

In unserem ersten Beispiel erstellen wir einen einfachen Buchservice, der Bücher auf reaktive Weise zu und von MongoDB aufbewahrt.

Navigieren Sie zunächst zur Spring Initializr-Homepage, auf der Sie ein Maven- Projekt mit Java auswählen und die aktuellste Version von Spring Boot auswählen (2.0.3 zum Zeitpunkt dieses Schreibens). Geben Sie Ihrem Projekt einen Gruppennamen wie "com.javaworld.webflux" und einen Artefaktnamen wie "bookservice". Erweitern Sie den Link Zur Vollversion wechseln, um die vollständige Liste der Abhängigkeiten anzuzeigen. Wählen Sie die folgenden Abhängigkeiten für die Beispielanwendung aus:

  • Web -> Reaktives Web : Diese Abhängigkeit umfasst Spring WebFlux.
  • NoSQL -> Reactive MongoDB : Diese Abhängigkeit enthält die reaktiven Treiber für MongoDB.
  • NoSQL -> Embedded MongoDB : Mit dieser Abhängigkeit können wir eine eingebettete Version von MongoDB ausführen, sodass keine separate Instanz installiert werden muss. Normalerweise wird dies zum Testen verwendet, aber wir werden es in unseren Versionscode aufnehmen, um die Installation von MongoDB zu vermeiden.
  • Kern -> Lombok : Die Verwendung von Lombok ist optional, da Sie es nicht benötigen, um eine Spring WebFlux-Anwendung zu erstellen. Der Vorteil von Project Lombok ist , dass es Ihnen Anmerkungen zu Klassen hinzufügen kann , die automatisch Getter und Setter, Konstrukteure generieren, hashCode(), equals()und vieles mehr.

Wenn Sie fertig sind, sollten Sie etwas Ähnliches wie in Abbildung 4 sehen.

Steven Haines

Wenn Sie auf Projekt generieren klicken, wird eine Zip-Datei heruntergeladen, die Ihren Projektquellcode enthält. Entpacken Sie die heruntergeladene Datei und öffnen Sie sie in Ihrer bevorzugten IDE. Wenn Sie IntelliJ verwenden, wählen Sie Datei und dann Öffnen und navigieren Sie zu dem Verzeichnis, in dem die heruntergeladene Zip-Datei dekomprimiert wurde.

Sie werden feststellen, dass Spring Initializr zwei wichtige Dateien generiert hat:

  1. Eine Maven- pom.xmlDatei, die alle erforderlichen Abhängigkeiten für die Anwendung enthält.
  2. BookserviceApplication.javaDies ist die Spring Boot-Starterklasse für die Anwendung.

Listing 1 zeigt den Inhalt der generierten Datei pom.xml.