前言
文中部分内容翻译自Reactor Guide,对Reactor Guide中举的一些例子做了一些修改和增减,更方便大家的理解。
从命令式编程刚开始接触Reactor对于异常的处理可能会有些不知所措。有很多人说,刚毕业的同学和工作了几年的同学在处理异常上会有很大的区别。是因为刚毕业的同学可能对于一些异常不会做处理。同样在我们实际的项目中,异常如果处理的不恰当会引发很重大的问题。所以,本篇就向大家介绍一下命令式的异常处理对应的响应式是如果操作的呢?
在开始之前,我们首先应该明确一点。在响应式序列中的任何异常都是一个终止事件,即使使用了处理异常的操作也不会让原序列继续执行。它会将onError信号转变为一个新的序列去替换已经跑出异常原本的序列。在使用响应式的方式处理异常的过程中也同样可以使用命令式编程的方式处理异常。
我们处理异常的场景中一般会有如下几种情况:
- 不做异常处理,发生异常中断操作。
- 捕获异常,返回一个默认静态常量。
- 捕获异常,执行可选择的路径的回调方法。
- 捕获异常,包装为另外一个业务异常然后再抛出。
- 捕获异常,记录异常的信息然后再次抛出。
- 使用finally模块处理一些事情,比如关闭文件,释放资源等。
这些情况在Reactor中都有相对应的处理方式。
1.不做异常处理,发生异常中断操作。
下面是一个命令式的例子,循环处理一些任务。当发生异常的时候它没有做任何的捕获直接中断操作。如果在doSomethingDangerous中没有异常发生那程序会很顺利的一直执行到for循环结束。如果发生了异常循环会立刻终止。
try {
for (int i = 1; i < 11; i++) {
String v1 = doSomethingDangerous(i);
String v2 = doSecondTransform(v1);
System.out.println("RECEIVED " + v2);
}
} catch (Throwable t) {
System.err.println("CAUGHT " + t);
}
->Reactor对应的处理方式:
Flux<String> s = Flux.range(1, 10)
.map(v -> doSomethingDangerous(v))
.map(v -> doSecondTransform(v));
s.subscribe(value -> System.out.println("RECEIVED " + value),
error -> System.err.println("CAUGHT " + error)
);
2.捕获异常,返回一个默认静态常量
下面是一个命令式的程序。它执行某个方法并返回结果,如果方法抛出异常,会被拦截并返回一个默认值。
try {
return doSomethingDangerous(10);
}
catch (Throwable error) {
return "RECOVERED";
}
->Reactor对应的处理方式
Mono.just(10)
.map(this::doSomethingDangerous)
.onErrorReturn("RECOVERED");
在响应式编程中还提供了另外一种方式,只有在满足某种条件下才返回默认值,否则抛出异常。
//在异常的信息为boom10的时候返回recovered10,否则抛出异常
Mono.just(10)
.map(this::doSomethingDangerous)
.onErrorReturn(e -> e.getMessage().equals("boom10"), "recovered10");
大家应该还记得在开篇关于Reactor在发生异常时,它就原本序列就结束了。我们可以通过下面的这个例子来验证。
Flux<String> s = Flux.range(1, 10)
.map(v -> {
if (v == 2) {
throw new BusinessException("1异常");
}
System.out.println("第一个map:" + v);
return v;
}).onErrorReturn(-1)
.map(v -> {
if (v == 3) {
throw new BusinessException("2异常");
}
System.out.println("第二个 map:" + v);
return v.toString();
});
s.subscribe(value -> System.out.println("RECEIVED " + value),
error -> System.err.println("CAUGHT " + error)
);
执行结果为:
第一个map:1
第二个 map:1
RECEIVED 1
第二个 map:-1
RECEIVED -1
从结果我们可以看出,发生异常后使用默认值返回循环也一样终止了。所以,大家在使用Reactor的这些特性处理异常时一定要清楚自己的方式会产生怎样的行为。那在Reactor中发生异常了还想继续执行有么有办法呢?
Flux<String> s = Flux.range(1, 10)
.map(v -> {
try {
if (v == 2) {
throw new BusinessException("1异常");
}
} catch (Exception e) {
}
System.out.println("第一个map:" + v);
return v;
}).onErrorReturn(-1)
.map(v -> {
if (v == 3) {
throw new BusinessException("2异常");
}
System.out.println("第二个 map:" + v);
return v.toString();
});
s.subscribe(value -> System.out.println("RECEIVED " + value),
error -> System.err.println("CAUGHT " + error)
);
执行结果:
第一个map:1
第二个 map:1
RECEIVED 1
第一个map:2
第二个 map:2
RECEIVED 2
第一个map:3
CAUGHT com.kuaidi.common.exception.BusinessException: 2异常
Disconnected from the target VM, address: '127.0.0.1:60707', transport: 'socket'
我们使用命令式的try catch捕获异常了就可以继续执行了。
3.根据不同的异常类型做不同的处理。
下面是一个命令式编程的例子:
try{
return doSomeThing();
}catch (BusinessException e){
//做一些事情
}catch (Exception e){
//做另外的一些事情
}
对应的响应式Reactor的处理方式:
Mono.just(doSomeThing("t")).onErrorResume(e -> {
if (e instanceof BusinessException) {
return xx;
}else if (e instanceof Exception){
return xx;
}
});
4.捕获异常,包装为另外一个业务异常然后再抛出。
下面是一个命令式编程的例子:
try {
return callExternalService(k);
}
catch (Throwable error) {
throw new BusinessException("error");
}
对应的响应式Reactor:
Flux.just("timeout1")
.flatMap(k -> callExternalService(k))
.onErrorResume(original -> Flux.error(
new BusinessException("error"))
);
除了使用onErrorResume还可以使用onErrorMap达到同样的效果。
Flux.just("timeout1")
.flatMap(k -> callExternalService(k))
.onErrorMap(original -> new BusinessException("error"));
5.记录错误并在抛出异常。
命令式编程的例子:
try {
return callExternalService(k);
}
catch (RuntimeException error) {
log("发生了一些错误 " + error.getMessage());
throw error;
}
在响应式中处理这种就很简单了。
Flux.just("key1", "key2", null)
.flatMap(k -> callExternalService(k).doOnError(e -> log.error("发生了一些错误" + e.getMessage())))
.subscribe(e -> System.out.println(e), e -> log.error("发生了一些错误2"));
我们可以在订阅处设置处理异常的方法,也可以在处理流的过程中使用doOnError处理。两种方式使用哪一种都可以。
6.使用finally模块处理一些事情,比如关闭文件,释放资源等。
下面是一个命令式的例子:
Stats stats = new Stats();
stats.startTimer();
try {
doSomethingDangerous();
}
finally {
stats.stopTimerAndRecordTiming();
}
对应的响应式的例子:
Flux.just("foo", "bar")
.map(k -> {
System.out.println("map:"+k);
return k;
})
.doFinally(type -> {
System.out.println("final:"+type);
})
.take(1).subscribe();
执行结果为:
map:foo
final:cancel
Flux.just("foo", "bar")
.map(k -> {
System.out.println("map:"+k);
return k;
})
.doFinally(type -> {
System.out.println("final:"+type);
}).subscribe();
执行结果:
map:foo
map:bar
final:onComplete
Flux.just("foo", "bar")
.map(k -> {
if(true){
throw new BusinessException("haha");
}
return k;
})
.doFinally(type -> {
System.out.println("final:"+type);
}).subscribe();
执行结果:
final:onError
通过上面的三个Reactor的例子可以看出,成功执行在doFinally传入的参数会是onComplete,取消和异常也有各自对应的字符串。我们这处理逻辑的时候可以根据不同的情况做不同的处理。
感谢阅读,希望对你有帮助。
参考资料
https://projectreactor.io/docs/core/release/reference/#flux
如果觉得写得不错,请作者喝杯奈雪吧~