前沿拓展:
初识响应式编程的时候,除了从命令式的思维方式转变为函数式的编程方式外,其中有一个很大的不适应的地方就是在面对异常时该怎么处理,尤其是面对检查异常(Checked Exception)时更是不知所措。在遇到异常时,我们通用的处理方式就是打日志、降级兜底、重试三板斧,本文通过Project Reactor的文档以及源码来深入解读,在reactor中是如何优雅地实现这异常处理三板斧。
在介绍怎么使用前,我们先回顾下在用reactor编程的时候,遇到的几个问题:
遇到异常时,如果能处理,我该怎么兜底/降级遇到无法处理的异常时,我该怎么打印日志,并往外抛遇到声明了检查异常的方法时,该怎么处理如果调用失败了(如请求超时),该如何重试如果出现异常了,流里面的后续数据还会继续发送吗异常处理的底层机制
在回答这些问题,就需要我们第一对reactor处理异常的机制要有理解。先说结论,如文档上说的:
Before you learn about error-handling operators, you must keep in mind that any error in a reactive sequence is a terminal event. Even if an error-handling operator is used, it does not let the original sequence continue. Rather, it converts the 与命令式编程有throws关键字声明不同,reactor中处理检查异常都必须用try-catch来处理,处理的方式有以下三种:
捕获到异常并从中恢复。序列继续正常的进行。捕获异常,将其封装成一个 不检查 的异常,第二将其抛出(中断序列)。如果你需要返回一个 Flux(例如,在 flatMap 中),那么就用一个产生错误的 Flux 来封装异常,如下所示:return Flux.error(checkedException)。(这个序列也会终止。)
这三种方式中,其中最常见也最常用的方式就是第二种,将检查异常转化为非检查异常,如throw new RuntimeException(e)。但是reactor提供了辅助工具类Exceptions,进而可以相对优雅简洁的进行统一处理。如以下这个例子
public String convert(int i) throws IOException {
if (i > 3) {
throw new IOException("boom " + i);
}
return "OK " + i;
}
Flux<String> converted = Flux
.range(1, 10)
.map(i -> {
try { return convert(i); }
catch (IOException e) { throw Exceptions.propagate(e); }
});
converted.subscribe(
v -> System.out.println("RECEIVED: " + v),
e -> {
if (Exceptions.unwrap(e) instanceof IOException) {
System.out.println("Something bad happened with I/O");
} else {
System.out.println("Something bad happened");
}
}
);
由于convert声明了检查异常IOException,所以必须要try-catch住,再利用Exceptions.propagate来封装为非检查异常。相比于直接用throw new RuntimeException(e),利用Exceptions的好处在onError处理阶段可以用Exceptions.unwrap()方法来获取内部真实抛出的异常,体现了利用工具类的好处——简洁明了。
小编综合来说
本文先从reactor异常处理的底层机制讲起,讲清楚了一个基本概念:只要出现异常,不管如何处理,旧的流都已经结束,接下来处理的都是新的流。在这基础上,按命令式编程中的try-catch-finally的方式,用reactor的方式进行了一一替代介绍,希望通过对比的方式,能更好的掌握在reactor中如何优雅的处理异常。
拓展知识:
原创文章,作者:九贤生活小编,如若转载,请注明出处:http://www.wangguangwei.com/122673.html