
本文共 3625 字,大约阅读时间需要 12 分钟。
前言
从命令式编程初次接触Reactor的异常处理可能会感到无从下手。许多人认为,处理异常对于刚毕业的学生和工作几年的经验丰富的程序员存在很大差异。对于刚入行的同学来说,面对某些异常可能不会做任何处理。而在实际项目中,异常处理不当可能会导致严重的问题。因此,本文将向大家介绍命令式编程和响应式编程在异常处理方面的不同。
开始之前必须明确的一点
在响应式序列中,任何异常都是一个终止事件,无论使用何种异常处理操作,都不会让原序列继续执行。它会将onError信号转换为一个新的序列,替代原序列中发生异常的部分。在响应式处理异常时,也可以使用命令式编程的方式进行处理。
常见的异常处理场景
在处理异常时,通常会遇到以下几种情况:
这些场景在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.range(1, 10) .map(v -> doSomethingDangerous(v)) .map(v -> doSecondTransform(v)) .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");
在响应式编程中,还提供了另一种方式,只有在满足某种条件下才返回默认值,否则抛出异常。
Mono.just(10) .map(this::doSomethingDangerous) .onErrorReturn(e -> e.getMessage().equals("boom10"), "recovered10");
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; } return null; });
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();}
Reactor对应的处理方式:
Flux.just("foo", "bar") .map(k -> { System.out.println("map:" + k); return k; }) .doFinally(type -> { System.out.println("final:" + type); }) .take(1) .subscribe();
执行结果为:
map:foofinal:cancel
另一个例子:
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,取消和异常也有各自对应的字符串。我们在处理逻辑时可以根据不同的情况做不同的处理。
结语
感谢阅读,希望对你有帮助。