Reaktive Programmierung in Java mit Project Reactor
\n\nReaktive Programmierung ist ein Entwicklungsparadigma, das auf asynchrone Datenströme und die Weitergabe von Änderungen ausgerichtet ist. In Java wird dieses Konzept durch die Spezifikation Reactive Streams und ihre bekannteste Implementierung, die Bibliothek Project Reactor (Teil des Spring WebFlux-Ökosystems), umgesetzt.
\n\nIm Gegensatz zu traditionellem imperativem Code, bei dem ein Programm Befehle sequenziell ausführt, ermöglicht der reaktive Ansatz die Verarbeitung von Daten, sobald sie eintreffen, ohne dabei Ausführungsthreads zu blockieren. Dies ist besonders wichtig für hochlastige Systeme, Microservices und Anwendungen mit einer großen Anzahl gleichzeitiger Anfragen.
\n\nGrundlegende Komponenten von Reactor
\n\nDie Bibliothek bietet zwei Haupttypen von reaktiven Sequenzen:
\n\n- \n
- Mono<T> – stellt einen Strom dar, der 0 oder 1 Element enthalten kann. Wird für einzelne asynchrone Operationen verwendet (z. B. HTTP-Anfrage, Abrufen eines Datensatzes aus der Datenbank). \n
- Flux<T> – stellt einen Strom dar, der von 0 bis N Elemente enthalten kann. Geeignet für Sammlungen, Ereignisströme oder Daten, die in Teilen eintreffen. \n
Beide Typen implementieren das Interface Publisher aus der Reactive-Streams-Spezifikation und unterstützen den Mechanismus des Backpressure (Gegendruck) – die Möglichkeit des Abonnenten, die Geschwindigkeit des Datenempfangs zu steuern.
Beispiel: Erstellen und Verarbeiten eines Flux
\n\nBetrachten wir ein einfaches Beispiel zum Erstellen eines reaktiven Zahlenstroms und dessen Verarbeitung:
\n\nimport reactor.core.publisher.Flux;\n\npublic class ReactorExample {\n public static void main(String[] args) {\n Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);\n\n numbers\n .map(n -> n * 2) // Transformation\n .filter(n -> n % 3 == 0) // Filterung\n .subscribe(\n data -> System.out.println("Empfangen: " + data),\n error -> System.err.println("Fehler: " + error),\n () -> System.out.println("Abgeschlossen!")\n );\n }\n}\n\n\nIn diesem Beispiel:
\n- \n
Flux.just()erstellt einen Strom aus fünf Zahlen. \n map()multipliziert jedes Element mit 2. \n filter()behält nur Zahlen, die durch 3 teilbar sind. \n subscribe()startet die Verarbeitung – gibt das Ergebnis aus, behandelt Fehler und signalisiert den Abschluss. \n
Asynchronität und Arbeiten mit Mono
\n\nReactor ermöglicht die Ausführung asynchroner Operationen ohne Blockierung von Threads. Zum Beispiel die Simulation einer Anfrage an einen entfernten Dienst:
\n\nimport reactor.core.publisher.Mono;\nimport java.time.Duration;\n\npublic class AsyncExample {\n public static void main(String[] args) throws InterruptedException {\n Mono<String> asyncResult = Mono.fromSupplier(() -> {\n try {\n Thread.sleep(1000); // Verzögerung simulieren\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n return "Daten empfangen";\n }).subscribeOn(Schedulers.boundedElastic()); // in einem anderen Thread ausführen\n\n System.out.println("Anfrage gesendet...");\n \n asyncResult.subscribe(System.out::println);\n \n Thread.sleep(2000); // auf Abschluss warten\n }\n}\n\n\nDer entscheidende Punkt: Der Aufruf von .subscribeOn() verlagert die Ausführung der teuren Operation in einen separaten Thread, ohne den Hauptthread zu blockieren. Der Abonnent erhält das Ergebnis asynchron.
Fehlerbehandlung in reaktiven Strömen
\n\nIm Gegensatz zu try-catch werden Fehler in der reaktiven Programmierung über Operatoren behandelt:
\n\nFlux<String> fluxWithError = Flux.just("A", "B", "C")\n .map(s -> {\n if ("B".equals(s)) t