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));
}
});
}
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()));
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.
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 sollteif
, und nicht durch Ausnahmebehandlung. Ich werde überprüfenswitfhIfEmpty
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 wirdswitchIfEmpty
. Wie auch immer, wenn Sie Fehler behandeln möchten, wenn Sie danach suchen, können Sie verwendenonErrorResume()
und entsprechend handhaben oder Sie können auch verwendenonErrorReturn()
. Führung– Barat
26. Januar 2019 um 16:23 Uhr