Spring Webflux с Spring MVC - пустые значения при использовании doOnEach - PullRequest
0 голосов
/ 13 мая 2018

Я немного экспериментировал с 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");
}

userReposutory - это пользовательский* Основанная на ConcurrentHashMap реализация.Здесь вы можете найти метод findAll:

@Override
public Flux<User> findAll() {
    return Flux.fromIterable(this.users.values());
}

Всякий раз, когда я пытаюсь вернуться для доступа к представлению "пользователи / список", кажется, что все работает правильно.

Но, еслиЯ пытаюсь переписать контроллер, используя идиоматический реактивный подход, начинают появляться проблемы:

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

Если я попадаю на конечную точку, я получаю это в логах:

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)

Видимо,какой-то бродячий 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");
}

Та же проблема, но ... если я сжимаю все в вызове map(), все снова работает:

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

Хотяразмещение побочных эффектов в map не является оптимальным.

Есть идеи, что не так с doOnEach() здесь?

1 Ответ

0 голосов
/ 13 мая 2018

Очень хороший вопрос. Давайте посмотрим, что JavaDocs говорит о doOnEach:

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

Добавление поведения, запускаемого, когда моно испускает предмет, не удается с ошибкой или завершается успешно. Все эти события представляется как Signal, который передается обратному вызову побочного эффекта

Любопытный. users в doOnEach(users -> ...) - это не List<User>, а Signal<List<User>>. Этот объект Signal<T> не будет нулевым, что объясняет, почему методы filter во второй версии не работают.

JavaDocs для Signal<T> говорит, что метод get() явно помечен как @Nullable и будет возвращать ненулевое значение только при поступлении следующего элемента. Если генерируется сигнал завершения или ошибки, он вернет null.

Решения:

  1. Вместо этого используйте doOnNext: вас интересует следующее значение, а не какой-либо сигнал, исходящий из исходного потока.
  2. Выполните нулевую проверку в doOnEach лямбда: это тоже будет работать, но, поскольку вас не интересуют другие события, это излишне.
...