Я немного экспериментировал с 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()
здесь?