Beispiel für die Integration von Vertx und RxJava

Beispiel für die Integration von Vertx und RxJava

1. Überblick

RxJava ist eine beliebte Bibliothek zum Erstellen von asynchronen und ereignisbasierten Programmen. Sie lässt sich von den Hauptideen der InitiativeReactive Extensionsinspirieren.

Vert.x, ein Projekt unter dem Dach vonEclipse, bietet mehrere Komponenten, die von Grund auf neu entwickelt wurden, um das reaktive Paradigma voll auszuschöpfen.

Zusammengenommen könnten sie sich als gültige Grundlage für jedesJava-Programm erweisen, das reaktiv sein muss.

In diesem Artikel laden wir eine Datei mit einer Liste von Städtenamen und drucken für jeden von ihnen aus, wie lange ein Tag von Sonnenaufgang bis Sonnenuntergang dauert.

Wir verwenden Daten aus der Öffentlichkeitwww.metaweather.comREST API - um die Länge des Tageslichts zu berechnen, undRxJava mitVert.x, um dies auf rein reaktive Weise zu tun.

2. Maven-Abhängigkeit

Beginnen wir mit dem Importieren vonvertx-rx-java2:


    io.vertx
    vertx-rx-java2
    3.5.0-Beta1

Zum Zeitpunkt des Schreibens ist die Integration zwischenVert.x und dem neuerenRxJava 2 nur als Beta-Version verfügbar, die jedoch für das von uns erstellte Programm stabil genug ist.

Beachten Sie, dassio.vertx:vertx-rx-java2 vonio.reactivex.rxjava2:rxjava abhängt, sodass kein explizites Paket fürRxJavaexplizit importiert werden muss.

Die neueste Version der Integration vonVert.x mitRxJava finden Sie aufMaven Central.

3. Installieren

Wie in jeder Anwendung, dieVert.x,verwendet, erstellen wir das Objektvertx, den Haupteinstiegspunkt für alle Funktionen vonVert.x:

Vertx vertx = io.vertx.reactivex.core.Vertx.vertx();

Die Bibliothekvertx-rx-java2 bietet zwei Klassen:io.vertx.core.Vertx undio.vertx.reactivex.core.Vertx. Während der erste der übliche Einstiegspunkt für Anwendungen ist, die eindeutig aufVert.x basieren, ist der letztere derjenige, den wir verwenden müssen, um die Integration mitRxJava zu erhalten.

Wir definieren weiterhin Objekte, die wir später verwenden werden:

FileSystem fileSystem = vertx.fileSystem();
HttpClient httpClient = vertx.createHttpClient();

Vert.xFileSystem ermöglicht reaktiven Zugriff auf das Dateisystem, währendVert.xHttpClient dasselbe fürHTTP tut.

4. Reaktive Kette

In einem reaktiven Kontext ist es einfach, mehrere einfachere reaktive Operatoren zu verketten, um eine aussagekräftige Berechnung zu erhalten.

Machen wir das fürour example:

fileSystem
  .rxReadFile("cities.txt").toFlowable()
  .flatMap(buffer -> Flowable.fromArray(buffer.toString().split("\\r?\\n")))
  .flatMap(city -> searchByCityName(httpClient, city))
  .flatMap(HttpClientResponse::toFlowable)
  .map(extractingWoeid())
  .flatMap(cityId -> getDataByPlaceId(httpClient, cityId))
  .flatMap(toBufferFlowable())
  .map(Buffer::toJsonObject)
  .map(toCityAndDayLength())
  .subscribe(System.out::println, Throwable::printStackTrace);

Lassen Sie uns nun untersuchen, wie jeder logische Codeabschnitt funktioniert.

5. Stadtnamen

Der erste Schritt ist das Lesen einer Datei mit einer Liste von Städtenamen, einem Namen pro Zeile:

fileSystem
 .rxReadFile("cities.txt").toFlowable()
 .flatMap(buffer -> Flowable.fromArray(buffer.toString().split("\\r?\\n")))

Die MethoderxReadFile() liest reaktiv eine Datei und gibtRxJavaSingle<Buffer> zurück. Wir haben also die Integration erhalten, die wir suchen: die Asynchronität vonVert.x in einer Datenstruktur vonRxJava.

Es gibt nur eine Datei, daher erhalten wir eine einzelne Emission vonBuffer mit dem vollständigen Inhalt der Datei. Wir konvertieren diese Eingabe inRxJavaFlowable und ordnen die Zeilen der Datei flach zu, umFlowable zu erhalten, die stattdessen für jeden Städtenamen ein Ereignis ausgeben.

6. JSON City Descriptor

Wenn Sie den Städtenamen haben, besteht der nächste Schritt darin,Metaweather REST API zu verwenden, um den Identifizierungscode für diese Stadt zu erhalten. Diese Kennung wird dann verwendet, um die Sonnenaufgangs- und Sonnenuntergangszeiten für die Stadt abzurufen. Setzen wir die Anrufungskette fort:

Setzen wir die Anrufungskette fort:

.flatMap(city -> searchByCityName(httpClient, city))
.flatMap(HttpClientResponse::toFlowable)

Die MethodesearchByCityName() verwendet dieHttpClient, die wir im ersten Schritt erstellt haben, um den DienstRESTaufzurufen, der die Kennung einer Stadt angibt. Dann erhalten wir mit den zweitenflatMap(), dieBuffer, die die Antwort enthalten.

Führen Sie diesen Schritt aus, indem Sie den Körper vonsearchByCityName()chreiben:

Flowable searchByCityName(HttpClient httpClient, String cityName) {
    HttpClientRequest req = httpClient.get(
        new RequestOptions()
          .setHost("www.metaweather.com")
          .setPort(443)
          .setSsl(true)
          .setURI(format("/api/location/search/?query=%s", cityName)));
    return req
      .toFlowable()
      .doOnSubscribe(subscription -> req.end());
}

Vert.xHttpClient gibtRxJavaFlowable zurück, das die reaktiveHTTPAntwort abgibt. Dies gibt wiederum den Körper der Antwort aus, aufgeteilt inBuffers.

Wir haben eine neue reaktive Anfrage an die richtige URL erstellt, aber wir haben festgestellt, dassVert.x requires the HttpClientRequest.end() method to be invoked signalisiert, dass die Anfrage gesendet werden kann, und dass mindestens ein Abonnement erforderlich ist, bevorend() erfolgreich aufgerufen werden können.

Eine Lösung, um dies zu erreichen, besteht darin,RxJavadoOnSubscribe() zu verwenden, umend() aufzurufen, sobald ein Verbraucher sich anmeldet.

7. Stadtkennungen

Wir müssen jetzt nur noch den Wert derwoeid-Eigenschaft des zurückgegebenenJSON-Objekts abrufen, das die Stadt durch eine benutzerdefinierte Methode eindeutig identifiziert:

.map(extractingWoeid())

Die MethodeextractingWoeid() gibt eine Funktion zurück, die die Stadtkennung aus denJSON extrahiert, die in der Serviceantwort vonRESTenthalten sind:

private static Function extractingWoeid() {
    return cityBuffer -> cityBuffer
      .toJsonArray()
      .getJsonObject(0)
      .getLong("woeid");
}

Beachten Sie, dass wir die praktischentoJson…()-Methoden vonBuffer verwenden können, um schnell auf die benötigten Eigenschaften zuzugreifen.

8. Stadt Details

Setzen wir die reaktive Kette fort, um die benötigten Details ausREST API abzurufen:

.flatMap(cityId -> getDataByPlaceId(httpClient, cityId))
.flatMap(toBufferFlowable())

Lassen Sie uns diegetDataByPlaceId()-Methode detailliert beschreiben:

static Flowable getDataByPlaceId(
  HttpClient httpClient, long placeId) {

    return autoPerformingReq(
      httpClient,
      format("/api/location/%s/", placeId));
}

Hier haben wir den gleichen Ansatz verwendet, den wir im vorherigen Schritt eingeführt haben. getDataByPlaceId() gibtFlowable<HttpClientResponse> zurück. DieHttpClientResponse geben wiederum dieAPI-Antwort in Blöcken aus, wenn sie länger als einige Bytes sind.

Mit dertoBufferFlowable()-Methode reduzieren wir die Antwortblöcke in einen einzigen, um Zugriff auf das vollständige JSON-Objekt zu erhalten:

static Function>
  toBufferFlowable() {
    return response -> response
      .toObservable()
      .reduce(
        Buffer.buffer(),
        Buffer::appendBuffer).toFlowable();
}

9. Sonnenuntergangs- und Sonnenaufgangszeiten

Fügen wir die reaktive Kette weiter hinzu und rufen die Informationen, an denen wir interessiert sind, aus demJSON-Objekt ab:

.map(toCityAndDayLength())

Schreiben wir dietoCityAndDayLength()-Methode:

static Function toCityAndDayLength() {
    return json -> {
        ZonedDateTime sunRise = ZonedDateTime.parse(json.getString("sun_rise"));
        ZonedDateTime sunSet = ZonedDateTime.parse(json.getString("sun_set"));
        String cityName = json.getString("title");
        return new CityAndDayLength(
          cityName, sunSet.toEpochSecond() - sunRise.toEpochSecond());
    };
}

Es gibt eine Funktion zurück, die die inJSON enthaltenen Informationen abbildet, um einPOJO zu erstellen, das einfach die Zeit in Stunden zwischen Sonnenaufgang und Sonnenuntergang berechnet.

10. Abonnement

Die reaktive Kette ist abgeschlossen. Wir können jetzt die resultierendenFlowable mit einem Handler abonnieren, der die ausgegebenen Instanzen vonCityAndDayLength oder die Stapelverfolgung bei Fehlern ausgibt:

.subscribe(
  System.out::println,
  Throwable::printStackTrace)

Wenn wir die Anwendung ausführen, wird abhängig von der in der Liste enthaltenen Stadt und dem Datum, an dem die Anwendung ausgeführt wird, ein solches Ergebnis angezeigt:

In Chicago there are 13.3 hours of light.
In Milan there are 13.5 hours of light.
In Cairo there are 12.9 hours of light.
In Moscow there are 14.1 hours of light.
In Santiago there are 11.3 hours of light.
In Auckland there are 11.2 hours of light.

Die Städte können in einer anderen Reihenfolge als den in der Datei angegebenen angezeigt werden, da alle Anforderungen an dieHTTP API asynchron ausgeführt werden.

11. Fazit

In diesem Artikel haben wir gesehen, wie einfach es ist,Vert.x reaktive Module mit den Operatoren und logischen Konstrukten vonRxJava zu mischen.

Die von uns erstellte reaktive Kette hat gezeigt, wie einfach es ist, ein komplexes Szenario zu schreiben.

Wie immer ist der vollständige Quellcodeover on GitHub verfügbar.