Mono switchIfEmpty() wird immer aufgerufen

Lesezeit: 7 Minuten

Benutzeravatar von html_programmer
html_programmierer

Ich habe zwei Methoden.
Hauptmethode:

@PostMapping("/login")
public Mono<ResponseEntity<ApiResponseLogin>> loginUser(@RequestBody final LoginUser loginUser) {
    return socialService.verifyAccount(loginUser)
            .flatMap(socialAccountIsValid -> {
                if (socialAccountIsValid) {
                    return this.userService.getUserByEmail(loginUser.getEmail())
                            .switchIfEmpty(insertUser(loginUser))
                            .flatMap(foundUser -> updateUser(loginUser, foundUser))
                            .map(savedUser -> {
                                String jwts = jwt.createJwts(savedUser.get_id(), savedUser.getFirstName(), "user");
                                return new ResponseEntity<>(HttpStatus.OK);
                            });
                } else {
                    return Mono.just(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
                }
            });

}

Und diese aufgerufene Methode (der Dienst ruft eine externe API auf):

public Mono<User> getUserByEmail(String email) {
    UriComponentsBuilder builder = UriComponentsBuilder
            .fromHttpUrl(USER_API_BASE_URI)
            .queryParam("email", email);
    return this.webClient.get()
            .uri(builder.toUriString())
            .exchange()
            .flatMap(resp -> {
                if (Integer.valueOf(404).equals(resp.statusCode().value())) {
                    return Mono.empty();
                } else {
                    return resp.bodyToMono(User.class);
                }
            });
} 

Im obigen Beispiel switchIfEmpty() wird immer von der Hauptmethode aufgerufen, auch wenn ein Ergebnis mit Mono.empty() ist zurück gekommen.

Ich finde keine Lösung für dieses einfache Problem.
Folgendes geht auch nicht:

Mono.just(null) 

Weil die Methode a auslöst NullPointerException.

Was ich auch nicht verwenden kann, ist die flatMap-Methode, um das zu überprüfen foundUser ist Null.
Leider wird flatMap überhaupt nicht aufgerufen, falls ich zurückkomme Mono.empty()also kann ich auch hier keine Bedingung hinzufügen.

@SimY4

   @PostMapping("/login")
    public Mono<ResponseEntity<ApiResponseLogin>> loginUser(@RequestBody final LoginUser loginUser) {
        userExists = false;
        return socialService.verifyAccount(loginUser)
                .flatMap(socialAccountIsValid -> {
                    if (socialAccountIsValid) {
                        return this.userService.getUserByEmail(loginUser.getEmail())
                                .flatMap(foundUser -> {
                                    return updateUser(loginUser, foundUser);
                                })
                                .switchIfEmpty(Mono.defer(() -> insertUser(loginUser)))
                                .map(savedUser -> {
                                    String jwts = jwt.createJwts(savedUser.get_id(), savedUser.getFirstName(), "user");
                                    return new ResponseEntity<>(HttpStatus.OK);
                                });
                    } else {
                        return Mono.just(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
                    }
                });

    }

  • Ich bin mir nicht sicher, ob ich diesen Satz richtig verstehe. switchIfEmpty() is always called from the main method, even when a result with Mono.empty() is returned.. Es soll heißen, oder?

    – Barat

    26. Januar 2019 um 5:39 Uhr

  • Kannst du dein Problem etwas näher erläutern? ”switchIfEmpty() wird immer von der Hauptmethode aufgerufen, auch wenn ein Ergebnis mit Mono.empty() zurückgegeben wird”. Dies ist das erwartete Verhalten.

    – Prashant Pandey

    26. Januar 2019 um 8:21 Uhr

  • @Barath Was ich erreichen möchte, ist, dass ich, wenn der externe Dienst 404 zurückgibt, ein Mono mit Wert zurückgeben kann null aus der Dienstschicht, die von der Hauptmethode gehandhabt werden können. Ich denke, ich könnte auch einen Fehler werfen, aber lieber nicht. Der 404 sollte auf der Dienstebene behandelt werden, und wenn ein Benutzer nicht gefunden wird, ist dies Anwendungslogik, mit der ich meiner Meinung nach umgehen sollte if, und nicht durch Ausnahmebehandlung. Ich werde überprüfen switfhIfEmpty in den Dokumenten. Trotzdem ein Arbeitsvorschlag?

    – html_programmierer

    26. Januar 2019 um 15:06 Uhr


  • @PrashantPandey Siehe Kommentar oben.

    – html_programmierer

    26. Januar 2019 um 15:06 Uhr

  • @Trace, dein Code funktioniert immer noch, wenn 404 zurückkehrt Mono.empty() der anrufen wird switchIfEmpty. Wie auch immer, wenn Sie Fehler behandeln möchten, wenn Sie danach suchen, können Sie verwenden onErrorResume() und entsprechend handhaben oder Sie können auch verwenden onErrorReturn(). Führung

    – Barat

    26. Januar 2019 um 16:23 Uhr


Benutzeravatar von Alex
Alex

Das liegt daran, dass switchIfEmpty Mono “nach Wert” akzeptiert. Das heißt, noch bevor Sie Ihr Mono abonnieren, wird die Bewertung dieses alternativen Monos bereits ausgelöst.

Stellen Sie sich eine Methode wie diese vor:

Mono<String> asyncAlternative() {
    return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
        System.out.println("Hi there");
        return "Alternative";
    }));
}

Wenn Sie Ihren Code wie folgt definieren:

Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());

Es wird immer eine Alternative auslösen, egal was während der Stream-Erstellung passiert. Um dies zu beheben, können Sie die Auswertung eines zweiten Mono mit verschieben Mono.defer

Mono<String> result = Mono.just("Some payload")
        .switchIfEmpty(Mono.defer(() -> asyncAlternative()));

Auf diese Weise wird nur “Hallo” gedruckt, wenn eine Alternative angefordert wird

AKTUALISIERT:

Ein wenig auf meine Antwort eingehen. Das Problem, mit dem Sie konfrontiert sind, hängt nicht mit Reactor zusammen, sondern mit der Java-Sprache selbst und wie sie Methodenparameter auflöst. Sehen wir uns den Code aus dem ersten von mir bereitgestellten Beispiel an.

Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());

Wir können dies umschreiben in:

Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = asyncAlternative();
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);

Diese beiden Codeausschnitte sind semantisch äquivalent. Wir können sie weiter auspacken, um zu sehen, wo das Problem liegt:

Mono<String> firstMono = Mono.just("Some payload");
CompletableFuture<String> alternativePromise = CompletableFuture.supplyAsync(() -> {
        System.out.println("Hi there");
        return "Alternative";
    }); // future computation already tiggered
Mono<String> alternativeMono = Mono.fromFuture(alternativePromise);
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);

Wie Sie sehen können, wurde die zukünftige Berechnung bereits an dem Punkt ausgelöst, an dem wir mit dem Komponieren unserer beginnen Mono Typen. Um unerwünschte Berechnungen zu vermeiden, können wir unsere Zukunft in eine verzögerte Bewertung einpacken:

Mono<String> result = Mono.just("Some payload")
        .switchIfEmpty(Mono.defer(() -> asyncAlternative()));

Welche wird auspacken

Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = Mono.defer(() -> Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
        System.out.println("Hi there");
        return "Alternative";
    }))); // future computation defered
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);

Im zweiten Beispiel ist die Zukunft in einem faulen Lieferanten gefangen und wird nur dann zur Ausführung eingeplant, wenn sie angefordert wird.

AKTUALISIERT: 2022:

Seit einiger Zeit kommt Project Reactor mit einer alternativen API, um eifrig berechnete Futures zu verpacken, was zum selben Ergebnis führt – eifrige Berechnungen in einem faulen Lieferanten einfangen:

Mono<String> result = Mono.just("Some payload")
        .switchIfEmpty(Mono.fromCompletionStage(() -> alternativePromise()));

  • Ich habe dies versucht, aber die Methode, wie Sie definieren asyncAlternative()wird immer trotz der ausgelöst Mono.defer().

    – html_programmierer

    30. Januar 2019 um 19:25 Uhr

  • @Trace kannst du mir zeigen, was du versucht hast? Weil der einzige Zweck von defer darin besteht, keine vorzeitige Auswertung zuzulassen.

    – Alex

    30. Januar 2019 um 19:34 Uhr

  • Siehe aktualisierten Beitrag. Im Debug-Modus habe ich gesehen, dass die Mono.defer aufgerufen wird, aber das ändert nichts daran, dass es immer ausgeführt wird, selbst wenn this.userService.getUserByEmail(loginUser.getEmail()) kehrt nicht zurück Mono.empty().

    – html_programmierer

    30. Januar 2019 um 19:44 Uhr


  • It'll always trigger alternative no matter what during stream construction. Was nützt es dann. Es ist eine Methode namens switchIfEmpty. Manche Dinge ergeben wirklich keinen Sinn.

    – html_programmierer

    30. Januar 2019 um 19:54 Uhr

  • Ihre Antwort war richtig. Ich brach es zusammen, stellte sich heraus, dass der Grund dafür war switchIfEmpty ausgelöst wurde, weil in der Tat updateUser hat einen leeren Körper mit http-Statuscode zurückgegeben 204! Ich habe die API etwas widerwillig modifiziert, aber jetzt funktioniert sie korrekt. Danke dafür!

    – html_programmierer

    1. Februar 2019 um 0:35 Uhr

Für diejenigen, die trotz der gut abgestimmten Antwort immer noch nicht verstehen, warum ein solches Verhalten erfolgt:

Reaktorquellen (Mono.xxx & Flux.xxx) sind entweder:

  • Faul bewertet : Der Inhalt der Quelle wird nur ausgewertet/ausgelöst, wenn ein Abonnent ihn abonniert;

  • oder eifrig ausgewertet : Der Inhalt der Quelle wird sofort ausgewertet, noch bevor der Abonnent abonniert.

Ausdrücke wie Mono.just(xxx), Flux.just(xxx), Flux.fromIterable(x,y,z) sind gespannt.

Durch die Nutzung defer(), erzwingen Sie, dass die Quelle faul ausgewertet wird. Deshalb funktioniert die akzeptierte Antwort.

Also das tun:

 someMethodReturningAMono()
  .switchIfEmpty(buildError());

mit buildError() sich auf eine eifrige Quelle verlassen, um ein alternatives Mono zu erstellen wird immer vor dem Abonnement ausgewertet werden:

Mono<String> buildError(){
       return Mono.just("An error occured!"); //<-- evaluated as soon as read
}

Um dies zu verhindern, gehen Sie wie folgt vor:

 someMethodReturningAMono()
  .switchIfEmpty(Mono.defer(() -> buildError()));

Lesen Sie diese Antwort für mehr.

  • einfach erklärt. Vielen Dank.

    – Arundew

    7. September 2022 um 8:43 Uhr

1444480cookie-checkMono switchIfEmpty() wird immer aufgerufen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy