web-dev-qa-db-ja.com

Mono switchIfEmpty()は常に呼び出されます

2つの方法があります。
主な方法:

_@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));
                }
            });

}
_

そして、この呼び出されたメソッド(サービスは外部APIを呼び出します):

_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);
                }
            });
} 
_

上記の例では、switchIfEmpty()の結果が返された場合でも、Mono.empty()は常にmainメソッドから呼び出されます。

この単純な問題の解決策が見つかりません。
以下も機能しません:

_Mono.just(null) 
_

メソッドがnullpointerexceptionをスローするためです。

また、使用できないのは、foundUserがnullであることを確認するためのflatMapメソッドです。
残念ながら、Mono.empty()を返す場合、flatMapはまったく呼び出されないため、ここにも条件を追加できません。

どんな助けでもありがたいです。

@ 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));
                    }
                });

    }
_
16
Trace

これは、switchIfEmptyがMonoの「値」を受け入れるためです。モノを購読する前であっても、この代替モノの評価はすでにトリガーされています。

このような方法を想像してみてください:

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

次のようにコードを定義すると、

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

ストリームの構築中に何が発生しても、常に代替をトリガーします。これに対処するには、Mono.deferを使用して2番目のモノの評価を延期できます。

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

このように、代替が要求されたときにのみ「こんにちは」を印刷します

PD:

私の答えについて少し詳しく説明します。あなたが直面している問題はReactorに関連するのではなく、Java言語自体とそれがメソッドパラメータを解決する方法に関連しています。私が提供した最初の例のコードを調べてみましょう。

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

これを次のように書き直すことができます。

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

これら2つのコードスニペットは、意味的に同等です。それらをアンラップして、問題がどこにあるかを確認できます。

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);

ご覧のとおり、将来の計算は、Monoタイプの作成を開始した時点ですでにトリガーされています。不要な計算を防ぐために、将来を据え置き評価にラップすることができます。

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

どちらが展開するか

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);

2番目の例では、フューチャーはレイジーサプライヤーに閉じ込められ、要求される場合にのみ実行がスケジュールされます。

19
SimY4