Spring Webflux with Spring MVC - 使用 doOnEach 时发出空值
Spring Webflux with Spring MVC - nulls emitted when using doOnEach
我正在尝试使用 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());
}
每当我尝试 return 访问 "users/list" 视图时,一切似乎都正常工作。
但是,如果我尝试使用惯用的反应式方法重写控制器,问题就会开始出现:
@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[=15=](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()
有什么问题吗?
非常好的问题。让我们看看 JavaDocs 关于 doOnEach
的内容:
public final Mono<T> doOnEach(Consumer<? super Signal<T>>
signalConsumer)
Add behavior triggered when the Mono emits an item,
fails with an error or completes successfully. All these events are
represented as a Signal
that is passed to the side-effect callback
很好奇。 doOnEach(users -> ...)
中的 users
不是 List<User>
,而是 Signal<List<User>>
。此 Signal<T>
对象不会为空,这解释了为什么第二个版本中的 filter
方法不起作用。
JavaDocs for Signal<T>
表示 get()
方法被显式标记为 @Nullable
并且 return 只有在下一个项目到达时才会有一个非空值。如果产生了完成或错误信号,那么它将return null
.
解决方案:
- 改用
doOnNext
:您感兴趣的是下一个值,而不是来自源流的任何信号。
- 在
doOnEach
lambda 中进行空值检查:这也可以,但由于您对其他事件不感兴趣,因此是多余的。
我正在尝试使用 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());
}
每当我尝试 return 访问 "users/list" 视图时,一切似乎都正常工作。
但是,如果我尝试使用惯用的反应式方法重写控制器,问题就会开始出现:
@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[=15=](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()
有什么问题吗?
非常好的问题。让我们看看 JavaDocs 关于 doOnEach
的内容:
public final Mono<T> doOnEach(Consumer<? super Signal<T>> signalConsumer)
Add behavior triggered when the Mono emits an item, fails with an error or completes successfully. All these events are represented as a
Signal
that is passed to the side-effect callback
很好奇。 doOnEach(users -> ...)
中的 users
不是 List<User>
,而是 Signal<List<User>>
。此 Signal<T>
对象不会为空,这解释了为什么第二个版本中的 filter
方法不起作用。
JavaDocs for Signal<T>
表示 get()
方法被显式标记为 @Nullable
并且 return 只有在下一个项目到达时才会有一个非空值。如果产生了完成或错误信号,那么它将return null
.
解决方案:
- 改用
doOnNext
:您感兴趣的是下一个值,而不是来自源流的任何信号。 - 在
doOnEach
lambda 中进行空值检查:这也可以,但由于您对其他事件不感兴趣,因此是多余的。