我是项目Reactor的新手,并且通常是反应式编程。
我目前正在编写类似于以下代码:
Mono.just(userId)
.map(repo::findById)
.map(user-> {
if(user == null){
throw new UserNotFoundException();
}
return user;
})
// ... other mappings
这个例子可能很愚蠢,确实有更好的方法来实现这种情况,但是重点是:
在throw new
块中使用map
异常是错误的,还是应该用return Mono.error(new UserNotFoundException())
替换它?
这两种方式有什么实际区别吗?
答案 0 :(得分:17)
有几种方法可以被视为方便的异常抛出方法:
Flux/Mono.handle
可以简化元素处理(可能导致错误或空流)的一种方法是运算符handle
。
以下代码显示了如何使用它来解决我们的问题:
Mono.just(userId)
.map(repo::findById)
.handle((user, sink) -> {
if(!isValid(user)){
sink.error(new InvalidUserException());
} else if (isSendable(user))
sink.next(user);
}
else {
//just ignore element
}
})
我们可以看到,.handle
运算符需要传递BiConsumer<T, SynchronousSink<>
才能处理元素。在这里,我们必须在BiConsumer中设置参数。第一个元素是上游元素,第二个元素是SynchronousSink
,这有助于我们向下游同步提供元素。这样的技术扩展了提供元素处理的不同结果的能力。例如,在元素无效的情况下,我们可以向同一SycnchronousSync
提供错误,这将取消上游并向下游产生onError
信号。反过来,我们可以使用相同的handle
运算符来“过滤”。一旦执行了句柄BiConsumer
并且未提供任何元素,Reactor会将其视为一种过滤,并将为我们请求其他元素。最后,如果该元素有效,我们可以简单地调用SynchronousSink#next
并将其传播到下游或在其上应用一些映射,因此这里将handle
作为map
运算符。此外,我们可以安全地使用该运算符,而不会影响性能,并提供复杂的元素验证,例如元素验证或向下游发送错误。
#concatMap
+ Mono.error
抛出在映射过程中引发异常的选项之一是将map
替换为concatMap
。本质上,concatMap
所做的几乎与flatMap
相同。唯一的区别是concatMap
一次仅允许一个子流。这种行为大大简化了内部实现,并且不影响性能。因此,我们可以使用以下代码来以更实用的方式引发异常:
Mono.just(userId)
.map(repo::findById)
.concatMap(user-> {
if(!isValid(user)){
return Mono.error(new InvalidUserException());
}
return Mono.just(user);
})
在上面的示例中,如果用户无效,我们使用Mono.error
返回异常。我们可以使用Flux.error
对通量执行相同的操作:
Flux.just(userId1, userId2, userId3)
.map(repo::findById)
.concatMap(user-> {
if(!isValid(user)){
return Flux.error(new InvalidUserException());
}
return Mono.just(user);
})
注意,在两种情况下,我们都返回仅包含一个元素的 cold 流。在Reactor中,如果返回的流是冷的 scalar 流,有几个优化措施可以提高性能。因此,当需要更复杂的映射时,建议使用Flux / Mono concatMap
+ .just
,empty
,error
,最终可能会导致{{ 1}}或return null
。
注意!永远不要检查传入元素的可空性。由于该反应式流规范(请参阅Rule 2.13),因此Reactor项目将永远不会为您发送
throw new ...
值。因此,如果null
返回null,Reactor将抛出NPE例外。
repo.findById
比concatMap
好?本质上,flatMap
旨在合并一次执行的多个子流中的元素。这意味着flatMap在其下应具有异步流,因此它们可能处理多个线程上的数据,或者可能是多个网络调用。随后,这种期望对实现产生了很大的影响,因此flatMap
应该能够处理来自多个流(flatMap
)的数据(意味着并发数据结构的使用),如果有其他资源的消耗,则将元素排队流(意味着为每个子流的Thread
分配额外的内存),并且不违反反应式流规范规则(意味着实现起来非常复杂)。计算所有这些事实,以及我们将普通的Queue
操作(是同步的)替换为使用map
(不会改变执行的同步性)的更方便的引发异常的事实,会导致实际上,我们不需要这么复杂的运算符,我们可以使用更简单的Flux/Mono.error
,其设计用于一次异步处理单个流,并进行了一些优化以处理标量冷流。
concatMap
因此,在结果为空时引发异常的另一种方法是switchOnEmpty
运算符。以下代码演示了如何使用该方法:
switchOnEmpty
我们可以看到,在这种情况下,Mono.just(userId)
.flatMap(repo::findById)
.switchIfEmpty(Mono.error(new UserNotFoundExeception()))
的{{1}}中的repo::findById
应该作为返回类型。因此,如果找不到Mono
实例,则结果流将为空。因此,Reactor将调用备用User
,指定为User
参数。
它可能被视为可读性较低的代码或不良做法,但您可以按原样抛出异常。这种模式违反了反应式流规范,但是反应器会为您捕获引发的异常并将其作为Mono
信号传播到下游
switchIfEmpty
运算符以提供复杂的元素处理onError
+ .handle
,但这种技术最适合异步元素处理的情况。concatMap
时,请使用Mono.error
+ flatMap
Mono.error
作为返回类型是被禁止的,因此在下游flatMap
中使用Null
会得到意外的null
而不是map
onError