Spring Boot 始终调用单声道开关IfEmpty()

bf1o4zei  于 2022-11-05  发布在  Spring
关注(0)|答案(2)|浏览(139)

我有两个办法。
主要方法:

@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()始终从main方法调用,即使返回的结果为Mono.empty()
我找不到解决这个简单问题的办法。
以下也不起作用:

Mono.just(null)

因为方法会掷回NullPointerException
我也不能使用flatMap方法来检查foundUser是否为空。
遗憾的是,如果我返回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));
                    }
                });

    }
rekjcdws

rekjcdws1#

这是因为switchIfEmpty“按值”接受Mono,这意味着即使在你订阅Mono之前,这个替代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<String> result = Mono.just("Some payload")
        .switchIfEmpty(Mono.defer(() -> asyncAlternative()));

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

统一产品数据:

详细说明一下我的答案。您所面临的问题与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);

这两个代码片段在语义上是等价的。我们可以继续展开它们,看看问题出在哪里:

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类型时,未来的计算已经被触发了。为了防止不必要的计算,我们可以将未来的计算 Package 到一个延迟的求值中:

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

在第二个例子中,future被困在一个懒惰的供应商中,只有当它被请求时才被调度执行。

统一产品代码:2022

自从一段时间以来,project reactor提供了一个替代的API来 Package 急切计算的future,这导致了同样的结果-在一个懒惰的供应商中捕获急切计算:

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

46scxncf2#

对于那些,尽管投票结果很好的答案,仍然不明白为什么这样的行为:
React堆来源(Mono.xxx和Flux.xxx)为:

*延迟评估:- 仅当订户订阅源内容时才评估/触发源内容;

  • 热切评价:甚至在订户订阅之前就立即评估源的内容。

Mono.just(xxx)Flux.just(xxx)Flux.fromIterable(x,y,z)这样的表达式是急切的。
通过使用defer(),可以强制对源代码进行延迟求值,这就是为什么接受的答案有效的原因。
所以这样做:

someMethodReturningAMono()
  .switchIfEmpty(buildError());

buildError()依赖于急切的源来创建替代Mono将始终在订阅之前进行评估:

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

要防止出现这种情况,请执行以下操作:

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

阅读此answer了解更多信息。

相关问题