始终调用Mono switchIfEmpty()

Tra*_*ace 13 java rx-java spring-boot reactive spring-webflux

我有两种方法.
主要方法:

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

}
Run Code Online (Sandbox Code Playgroud)

这个被调用的方法(该服务调用外部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);
                }
            });
} 
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,switchIfEmpty()始终从main方法调用,即使Mono.empty()返回结果.

我无法找到这个简单问题的解决方案.
以下也不起作用:

Mono.just(null) 
Run Code Online (Sandbox Code Playgroud)

因为该方法将抛出nullpointerexception.

我也不能使用的是检查foundUsernull 的flatMap方法.
可悲的是,如果我返回,flatMap根本不会被调用Mono.empty(),所以我也不能在这里添加条件.

任何帮助表示赞赏.

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

    }
Run Code Online (Sandbox Code Playgroud)

Sim*_*mY4 14

这是因为switchIfEmpty接受Mono“按值”。这意味着,即使在您订阅单声道之前,此替代单声道的评估也已触发。

想象这样的方法:

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

如果您这样定义代码:

Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
Run Code Online (Sandbox Code Playgroud)

无论在流构建期间发生什么,它总是会触发替代方法。为了解决这个问题,您可以通过使用推迟对第二个单声道的评估Mono.defer

Mono<String> result = Mono.just("Some payload")
        .switchIfEmpty(Mono.defer(() -> asyncAlternative()));
Run Code Online (Sandbox Code Playgroud)

这样,仅当请求替代项时才打印“ Hi there”

UPD:

详细说明我的答案。您面临的问题与Reactor无关,而与Java语言本身以及它如何解析方法参数有关。让我们检查一下我提供的第一个示例中的代码。

Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
Run Code Online (Sandbox Code Playgroud)

我们可以将其重写为:

Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = asyncAlternative();
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
Run Code Online (Sandbox Code Playgroud)

这两个代码段在语义上是等效的。我们可以继续展开它们,以查看问题所在:

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);
Run Code Online (Sandbox Code Playgroud)

如您所见,当我们开始编写Mono类型时,将来的计算已经触发。为了防止不必要的计算,我们可以将我们的未来纳入延迟评估中:

Mono<String> result = Mono.just("Some payload")
        .switchIfEmpty(Mono.defer(() -> asyncAlternative()));
Run Code Online (Sandbox Code Playgroud)

哪个会解开

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);
Run Code Online (Sandbox Code Playgroud)

在第二个示例中,未来被困在一个懒惰的供应商中,并且仅在需要时才安排执行。

  • `无论在流构建过程中如何,它总是会触发替代。` 那有什么用。这是一个名为“switchIfEmpty”的方法。有些话真的说不通。 (3认同)
  • @Trace 你能告诉我你尝试过什么吗?因为 defer 的唯一目的是不允许评估在时间之前发生。 (2认同)
  • 你的回答是正确的。我拆解了一下,发现`switchIfEmpty`被触发的原因是因为`updateUser`实际上返回了一个空的body,http状态代码为`204`!我有点不情愿地修改了api,但现在它可以正常工作了。谢谢你! (2认同)

Phi*_*imo 6

对于那些尽管投票结果很好但仍然不明白为什么会出现这种行为的人:

Reactor 源(Mono.xxx 和 Flux.xxx)是:

  • 懒惰评估:仅当订阅者订阅时才评估/触发源的内容;

  • 急切地评估:甚至在订阅者订阅之前立即评估源的内容。

般的表情Mono.just(xxx)Flux.just(xxx)Flux.fromIterable(x,y,z)都渴望。

通过使用defer(),您可以强制延迟评估源。这就是接受的答案有效的原因。

所以这样做:

 someMethodReturningAMono()
  .switchIfEmpty(buildError());

Run Code Online (Sandbox Code Playgroud)

buildError()依赖于一个热心源以产生替代单声道将永远订阅之前进行评估:

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

Run Code Online (Sandbox Code Playgroud)

为了防止这种情况,请执行以下操作:

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

Run Code Online (Sandbox Code Playgroud)

阅读此答案以了解更多信息。