|
| 1 | +package dev.scx.exception; |
| 2 | + |
| 3 | +/// ScxWrappedException 是一个通用的运行时异常包装器, 主要用途 : |
| 4 | +/// |
| 5 | +/// - 1. 将受检异常包装为运行时异常. |
| 6 | +/// - 2. 对动态可执行单元 (如高阶函数, 装饰器模式) 进行异常隔离和标记. |
| 7 | +/// |
| 8 | +/// ### 设计动机 |
| 9 | +/// |
| 10 | +/// 设想一下有如下方法 |
| 11 | +/// ```java |
| 12 | +/// public void read(Func<byte[]> bytesConsumer, int length) throws IOException { |
| 13 | +/// // someCode |
| 14 | +/// } |
| 15 | +/// ``` |
| 16 | +/// 当我们在调用时就会遇到如下问题, 也就是异常来源的模糊性问题. |
| 17 | +/// ```java |
| 18 | +/// try { |
| 19 | +/// read(bytes -> { |
| 20 | +/// // someCode maybe throw IOException |
| 21 | +/// }, 1024); |
| 22 | +/// } catch (IOException e) { |
| 23 | +/// // 该如何区分这个 IOException 是 read 本身的 还是 bytesConsumer 的 ? |
| 24 | +/// e.printStackTrace(); |
| 25 | +/// } |
| 26 | +/// ``` |
| 27 | +/// |
| 28 | +/// 针对这种情况 我们创建 `ScxWrappedException` 用于包装 `bytesConsumer` 的异常, 以便调用者能够正确区分. |
| 29 | +/// |
| 30 | +/// ### 用法展示, 这里我展示 `两种推荐用法` . |
| 31 | +/// |
| 32 | +/// #### 用法 1 : 包装所有异常. |
| 33 | +/// |
| 34 | +/// 案例 1 : |
| 35 | +/// ```java |
| 36 | +/// public void read(Func<byte[]> bytesConsumer, int length) throws ScxWrappedException, IOException { |
| 37 | +/// // someCode |
| 38 | +/// try { |
| 39 | +/// bytesConsumer.apply(new byte[]{1,2,3}); |
| 40 | +/// } catch (Exception e) { |
| 41 | +/// throw new ScxWrappedException(e); |
| 42 | +/// } |
| 43 | +/// // someCode |
| 44 | +/// } |
| 45 | +/// ``` |
| 46 | +/// |
| 47 | +/// - 优点 : 异常隔离层级清晰, 调用方心智负担小. |
| 48 | +/// - 缺点 : 对于调用方 总是需要处理 ScxWrappedException 异常. |
| 49 | +/// |
| 50 | +/// #### 用法 2 : 只包装可能引发混淆的异常. |
| 51 | +/// |
| 52 | +/// 案例 1 : `动态可执行单元` 只抛出 RuntimeException. |
| 53 | +/// |
| 54 | +/// ```java |
| 55 | +/// // 假设 NoPermissionException 是一个 RuntimeException. |
| 56 | +/// public void checkPermission(Func<String[], RuntimeException> permissionsSupplier, String department) throws ScxWrappedException, NoPermissionException { |
| 57 | +/// // someCode |
| 58 | +/// try { |
| 59 | +/// permissionsSupplier.apply(); |
| 60 | +/// } catch (NoPermissionException e) { |
| 61 | +/// // 只包装可能混淆的异常 |
| 62 | +/// throw new ScxWrappedException(e); |
| 63 | +/// } catch (RuntimeException e) { |
| 64 | +/// // 此处的 catch 代码块也可以直接删除, 此处为了演示. |
| 65 | +/// throw e; |
| 66 | +/// } |
| 67 | +/// // someCode |
| 68 | +/// } |
| 69 | +/// ``` |
| 70 | +/// |
| 71 | +/// 案例 2 : `动态可执行单元` 支持抛出 泛型异常 (包含受检异常). |
| 72 | +/// |
| 73 | +/// ```java |
| 74 | +/// public <X extends Exception> void read(Func<byte[], X> bytesConsumer, int length) throws X, ScxWrappedException, IOException { |
| 75 | +/// // someCode |
| 76 | +/// try { |
| 77 | +/// bytesConsumer.apply(new byte[]{1,2,3}); |
| 78 | +/// } catch (Exception e) { |
| 79 | +/// // 包装易混淆异常 |
| 80 | +/// if(e instanceof IOException) { |
| 81 | +/// throw new ScxWrappedException(e); |
| 82 | +/// } |
| 83 | +/// // 其他异常直接抛出 |
| 84 | +/// throw e; |
| 85 | +/// } |
| 86 | +/// // someCode |
| 87 | +/// } |
| 88 | +/// ``` |
| 89 | +/// |
| 90 | +/// - 优点 : 对于不会混淆的异常 会直接穿透到调用者. 在异常层级上会更干净, 而且更符合所谓的函数式哲学. |
| 91 | +/// - 缺点 : 模糊了异常的层级, 对调用者来说 有些异常会包装有些不会包装, 可能造成一定程度的心智负担. |
| 92 | +/// - 小提示 1 : 在案例 2 中, 实际使用过程中 X 会被推断为 bytesConsumer 抛出的所有异常的最小共同父类. |
| 93 | +/// - 小提示 2 : 如果你的 `动态可执行单元` 永远不会抛出令人混淆的异常, 那么你实际上不需要这个类. |
| 94 | +/// |
| 95 | +/// @author scx567888 |
| 96 | +/// @version 0.0.1 |
| 97 | +public final class ScxWrappedException extends RuntimeException { |
| 98 | + |
| 99 | + public ScxWrappedException(String message, Throwable cause) { |
| 100 | + // 包装类 不允许空 cause |
| 101 | + if (cause == null) { |
| 102 | + throw new NullPointerException("cause must not be null"); |
| 103 | + } |
| 104 | + super(message, cause); |
| 105 | + } |
| 106 | + |
| 107 | + public ScxWrappedException(Throwable cause) { |
| 108 | + // 包装类 不允许空 cause |
| 109 | + if (cause == null) { |
| 110 | + throw new NullPointerException("cause must not be null"); |
| 111 | + } |
| 112 | + super(cause); |
| 113 | + } |
| 114 | + |
| 115 | + /// 获取真正的异常 |
| 116 | + public Throwable getRootCause() { |
| 117 | + var cause = this.getCause(); |
| 118 | + if (cause instanceof ScxWrappedException wrappedException) { |
| 119 | + return wrappedException.getRootCause(); |
| 120 | + } |
| 121 | + return cause; |
| 122 | + } |
| 123 | + |
| 124 | +} |
0 commit comments