Spring Webflux 与 Spring MVC - 使用 doOnEach 时发出空值

Grz*_*rek 5 spring-mvc project-reactor spring-webflux

我正在尝试使用 Spring Webflux 和 Spring MVC,遇到了一个有趣的案例。

从一个简单的控制器开始:

@GetMapping
public Mono<String> list(final Model model) {
    Flux<User> users = this.userRepository.findAll();
    model.addAttribute("users", users);
    return Mono.just("users/list");
}
Run Code Online (Sandbox Code Playgroud)

userReposutory是一个ConcurrentHashMap基于自定义的实现。在这里你可以找到findAll方法:

@Override
public Flux<User> findAll() {
    return Flux.fromIterable(this.users.values());
}
Run Code Online (Sandbox Code Playgroud)

每当我尝试返回访问“用户/列表”视图时,一切似乎都正常工作。

但是,如果我尝试使用惯用的反应式方法重写控制器,问题就会开始出现:

@GetMapping
public Mono<String> list(final Model model) {
    return this.userRepository.findAll()
      .collectList()
      .doOnEach(users -> model.addAttribute("users", users.get()))
      .map(u -> "users/list");
}
Run Code Online (Sandbox Code Playgroud)

如果我到达终点,我会在日志中得到这个:

java.lang.IllegalArgumentException: ConcurrentModel does not support null attribute value
    at org.springframework.util.Assert.notNull(Assert.java:193)
    at org.springframework.ui.ConcurrentModel.addAttribute(ConcurrentModel.java:75)
    at org.springframework.ui.ConcurrentModel.addAttribute(ConcurrentModel.java:39)
    at com.baeldung.lss.web.controller.UserController.lambda$list$0(UserController.java:37)
    at reactor.core.publisher.FluxDoOnEach$DoOnEachSubscriber.onError(FluxDoOnEach.java:132)
Run Code Online (Sandbox Code Playgroud)

显然,一些流浪者null正在那里。让我们急切地过滤掉所有这些:

@RequestMapping
public Mono<String> list(final Model model) {
    return this.userRepository.findAll()
      .filter(Objects::nonNull)
      .collectList()
      .filter(Objects::nonNull)
      .doOnEach(users -> model.addAttribute("users", users.get()))
      .map(u -> "users/list");
}
Run Code Online (Sandbox Code Playgroud)

同样的问题,但是......如果我在map()通话中压缩所有内容,一切都会再次正常:

@GetMapping
public Mono<String> list(final Model model) {
    return this.userRepository.findAll()
      .collectList()
      .map(users -> {
          model.addAttribute("users", users);
          return "users/list";
      });
}
Run Code Online (Sandbox Code Playgroud)

虽然,放置副作用map不是最佳的。

任何想法doOnEach()这里有什么问题?

Mur*_*kan 5

非常好的问题。让我们看看JavaDocs讲述了什么doOnEach

public final Mono<T> doOnEach(Consumer<? super Signal<T>> signalConsumer)

添加当 Mono 发出项目、因错误而失败或成功完成时触发的行为。所有这些事件都表示为Signal传递给副作用回调的 a

好奇的。该users 中doOnEach(users -> ...)不是一个List<User>而是一个Signal<List<User>>。这个Signal<T>对象不会为空,这就解释了为什么filter第二个版本中的方法不起作用。

为JavaDocs中Signal<T>说,该get()法明确标记为@Nullable将返回只有一个项目到达一个非空值。如果生成完成或错误信号,则它将返回null

解决方案:

  1. doOnNext改为使用:您对下一个值感兴趣,而不是来自源流的任何信号。
  2. doOnEachlambda 中进行空检查:这也可以,但由于您对其他事件不感兴趣,因此是多余的。

  • 谢谢,我来自 RxJava 世界,我没有意识到 `doOnEach` 旁边有 `doOnNext`!:) (2认同)