领先一步
VMware 提供培训和认证,为您的进步加速。
了解更多响应式 Spring 社区的各位朋友!
团队目前仍在全力投入 3.1 系列的开发,但我们也想让社区有机会先睹为快地了解未来 3.2 系列即将推出的内容。
特别是,计划在 3.2.0.RELEASE 版本中加入的一大亮点是我们一直称之为“错误模式”、“继续模式”,或者最近更官方的说法——“错误策略”。
说起来很简单:如果在操作符中执行的用户代码抛出异常后可以从中恢复,从而允许序列继续(*continue*)执行呢?
让我们举个例子,假设你有一个如下方法
public Flux<Integer> divide100By(Flux<Integer> dividers) {
return dividers.map(div -> 100 / div);
}
如果 `dividers` 源在某个时刻发出 `0`,那么结果 `Flux` 将立即因 `ArithmeticException` 的 `onError` 信号而终止。
如果源是 `Flux.range(0, 10)`,那么仍然有 9 个完全有效的值可以被映射(mapped)。
你如何才能做到只忽略这种瞬时异常(且仅此异常),并让后续有效值有机会得到处理呢?
在 Reactor 3.1 中,你可以通过使用 `flatMap` 为每个元素创建内部序列,并依次将错误恢复操作符应用于这些细粒度序列来应用一种变通方法。
public Flux<Integer> divide100By(Flux<Integer> dividers) {
return dividers.flatMap(div -> (1)
Mono.just(100 / div) (2)
.doOnError(e -> { (3)
if (e instanceof ArithmeticException) process(e); (4)
})
.onErrorResume(ArithmeticException.class, e -> Mono.empty()) (5)
);
}
我们不使用 `map`,而是使用 `flatMap`,为每个值生成一个小的内部 `Mono`
那个 `Mono` 本质上就是旧的 `map` 操作……
……并添加了错误恢复。
首先,我们确保在“恢复”之前处理(例如记录日志)`ArithmeticException`(且仅此异常)
然后我们使用带有 `Mono.empty()` 的 `onErrorResume` 来有效地忽略结果序列中的异常
这确实可行,但写起来有点麻烦(尽管 `compose` 和 `transform` 可以帮助实现这类代码的复用)。此外,我们从单个 `map` 操作符转变为带有多个内部操作符的 `flatMap`。
`flatMap` 比 `map` 有更多的开销,因为它需要协调多个源。尽管像操作符融合(operator fusion)这样的技术可以降低这个成本,但开销依然存在。
如果我们想进一步降低这种处理方式的开销,困难在于我们现在必须在每个操作符的实现层面进行工作。
链中的每个操作符都必须以某种方式知道异常应该被捕获但不通过 `onError` 传播,而是以不同的方式处理。这是一个相当大的变化,并且是横向的(transverse)!
请注意,这在概念上听起来很像 `filter`,但它是针对错误的。就像 `filter` 一样,这意味着一个操作符在处理其源的 `onNext` 抛出异常后继续处理时,也应该向其源请求至少一个额外的元素。
尽管它可以被隔离到特殊的执行路径中,但这仍然是对操作符核心实现的复杂更改。
接着是 API 的问题:将其作为构造函数参数或在 `Flux` 中添加带有“错误恢复”布尔值的额外重载会非常繁琐……我们真的需要为了支持这个特性而将 `Flux` API 中的方法数量翻倍吗?
幸运的是不必:从 3.1 版本开始,我们有了 `Context`,它是将此类信息传播到链中每个 (Reactor) 操作符的良好手段。
所以这就是我们为错误策略特性所采取的路径
支持仅添加到**特定操作符**(`map`、`filter`、`flatMap`、`handle` 等)。这些操作符有一个特殊的 javadoc 标签来记录这一事实。
通过在给定的 `Flux` 的 `Context` 中放入一个特殊键来激活该特性
每个支持的操作符在其 `onNext` 实现中都有一个特殊路径,它检查该键,如果找到,将改变其处理错误的方式。
该特性通过 `errorStrategyContinue()` API 向用户公开
它可以更细粒度:可以过滤哪些异常可以恢复,并且可以为此类恢复的异常设置自定义处理器。
重要
需要记住的一点是,由于这是通过 `Context` 激活的,该特性遵循与 `Context` 相同的传播规则。例如,它将在 `flatMap` 的内部序列上激活。如果这不是期望的行为,请在 flatMap 内部使用 `errorStrategyStop()` 恢复默认行为(这不会超出 flatMap 的范围)。**它也会向后传播,在 `errorStrategyXXX` 之前的操作符上激活**。
以下是我们的先前示例在使用错误策略后的样子
public Flux<Integer> divide100By(Flux<Integer> dividers) {
return dividers.map(div -> 100 / div) (1)
.errorStrategyContinue(ArithmeticException.class, (2)
(error, value) -> process(error)); (3)
}
回到简单的 `map`
我们只从 `ArithmeticException` 恢复
我们将此类异常传递给我们内部的处理器(注意,如果存在,我们还可以访问导致异常的原始值)
我们刚刚发布了 `3.2.0.M1` 里程碑版本[1],主要包含错误策略特性,我们希望您能对其进行测试?
注意
这是一个如此横向的变化,即使您不打算使用它,用该工件(artifact)运行您的测试也是有价值的,以验证如果您没有明确使用 `errorStrategyContinue()`,行为不应有任何变化(因为该特性包含在特定的执行路径中)。
**为了获取里程碑版本**,将 `repo.spring.io/milestone` 仓库添加到您的 Maven 或 Gradle 构建配置中,并获取 `reactor-core` `3.2.0.M1` 工件(artifact)
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.2.0.M1</version>
</dependency>
特别是对于该特性的 API,目前还没有最终确定。因此,如果您有任何反馈,请通过在 GitHub 上提出 issue 或在 Gitter 上讨论该特性,随时告知我们。
在此期间,祝您编码愉快!
1. 附注:这个里程碑版本提前发布了,当时 3.1.3.RELEASE 仍在开发中,因此请注意它不包含 3.1.3 及后续 3.1.x 版本中的所有修复。