addSuppressed异常抑制

addSuppressed异常抑制

天涯共此时

在了解异常抑制之前,先来看看Throwable的addSuppressed()方法的注释(机翻)

将指定的异常附加到为了传递此异常而被抑制的异常。此方法是线程安全的,通常由try-with-resources语句(自动和隐式)调用。
除非通过构造函数禁用抑制行为,否则将启用抑制行为。禁用抑制后,此方法除了验证其参数外不执行其他操作。
请注意,当一个异常导致另一个异常时,通常会捕获第一个异常,然后作为响应抛出第二个异常。换句话说,两个例外之间存在因果关系。相反,在某些情况下,可能在同级代码块中引发两个独立的异常,特别是在try-with-resources语句的try块和编译器生成的finally块中,这两个异常会关闭资源。在这些情况下,只能传播所引发的异常之一。在try-with-resources语句中,当有两个这样的异常时,将传播来自try块的异常,并将finally块的异常添加到由try块的异常抑制的异常列表中。当异常展开堆栈时,它可以累积多个受抑制的异常。
异常可能抑制了异常,但也可能是由另一个异常引起的。在创建异常时,从语义上知道异常是否有原因,这与异常是否会抑制其他异常不同,后者通常仅在引发异常后才能确定。

不看也无所谓,看一下小demo就能理解了。

我们在对异常进行处理的时候,有时候在finally和catch中也会出现异常,还需要在catch或者finally中再对新的异常进行处理。可是在catch或者finally中抛出的异常会把try代码块中的异常给抑制调。但我们最需要的异常信息其实是try代码块中抛出的异常。

比如下面这个/zero的异常

@Test
public void test1() {
     
    int a;
    try {
     
    	a = 3 / 0;
   		System.out.println(a);
    } catch (ArithmeticException zero) {
     
 	   zero.printStackTrace();
    }
}
java.lang.ArithmeticException: / by zero
	at com.springlearn.qiyan.base.ThrowTest.test1(ThrowTest.java:41)

我们想要在catch中处理调这个可能为0的情况,给他一个新的被除数x,现在有一个新的getX()方法去获取x。这个getX()也并不靠谱,他可能会返回null,所以我们在catch代码块中再去捕获这个新的空指针异常。

public Integer getX(){
     
    Random random = new Random();
    boolean b = random.nextBoolean();
    if(b){
     
        return null;
    }else{
     
        return 1;
    }
}

@Test
public void test() {
     
    int a;
    Integer x = getX();
    try {
     
        a = 3 / 0;
        System.out.println(a);
    } catch (ArithmeticException zero) {
     
        try {
     
            a = 3 / x;
            System.out.println(a);
        } catch (NullPointerException nullPoint) {
     
            nullPoint.printStackTrace();
        }

    }
}

当getX()返回null的时候:返回的异常是NullPointerException,而不是外层的ArithmeticException

java.lang.NullPointerException
	at com.springlearn.qiyan.base.ThrowTest.test(ThrowTest.java:26)

但是理想的状态应该是在外层的try代码块中实现对a的运算,即使出错,我们也应该去拿ArithmeticException去定位异常,空指针异常可能并不是我们想要捕获的。

这时候就可以使用Throwable中的addSuppressed方法去实现对两个异常的捕获

@Test
public void test3() {
     
    int a;
    Integer x = getX();
    try {
     
        a = 3 / 0;
        System.out.println(a);
    } catch (ArithmeticException zero) {
     
        try {
     
            a = 3 / x;
            System.out.println(a);
        } catch (NullPointerException nullPoint) {
     
            //看这里
            zero.addSuppressed(nullPoint);
            zero.printStackTrace();
        }
    }
}
java.lang.ArithmeticException: / by zero
	at com.springlearn.qiyan.base.ThrowTest.test3(ThrowTest.java:85)
	Suppressed: java.lang.NullPointerException
		at com.springlearn.qiyan.base.ThrowTest.test3(ThrowTest.java:89)

可以看到两个异常都被打印出来了。


这种catch或者finally中还要嵌套异常捕获的现象在对文件的处理中很常见:就比如说在finally中对外部资源进行关闭

@Test
public void test() {
     
    FileInputStream inputStream = null;
    try {
     
        inputStream = new FileInputStream("file.txt");
    } catch (FileNotFoundException e) {
     
        e.printStackTrace();
    } finally {
     
        if (inputStream != null) {
     
            try {
     
                inputStream.close();
            } catch (IOException e) {
     
                e.printStackTrace();
            }
        }

    }
}

这样写非常繁琐,jdk7中也提供了对try-with-resource语法去简化这样复杂的代码

@Test
public void test() {
     
    try (FileInputStream inputStream = new FileInputStream("file.txt")) {
     
        System.out.println(inputStream.read());
    } catch (IOException e) {
     
        e.printStackTrace();
    }
}

看一下编译后的class文件

@Test
public void test() {
     
    try {
     
        FileInputStream inputStream = new FileInputStream("file.txt");
        Throwable var2 = null;

        try {
     
            System.out.println(inputStream.read());
        } catch (Throwable var12) {
     
            var2 = var12;
            throw var12;
        } finally {
     
            if (inputStream != null) {
     
                if (var2 != null) {
     
                    try {
     
                        inputStream.close();
                    } catch (Throwable var11) {
     
                        //看这里
                        var2.addSuppressed(var11);
                    }
                } else {
     
                    inputStream.close();
                }
            }

        }
    } catch (IOException var14) {
     
        var14.printStackTrace();
    }

}

可以看到try-with-resource也是对异常进行了addSuppressed()操作


看完了demo,我们来看一下Throwable中的addSuppressed方法

首先我们先来看一下这个方法用到的几个集合和常量

//用来标识是一个被抑制的异常
private static final String SUPPRESSED_CAPTION = "Suppressed: ";
//定义一个大小为0且不可变的集合
private static final List<Throwable> SUPPRESSED_SENTINEL =
        Collections.unmodifiableList(new ArrayList<Throwable>(0));
//使suppressedExceptions指向上述那个不可变的集合
private List<Throwable> suppressedExceptions = SUPPRESSED_SENTINEL;

代码:

public final synchronized void addSuppressed(Throwable exception) {
     
    //不能抑制自身
    if (exception == this)
        throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);

    //被抑制的异常不能为空
    if (exception == null)
        throw new NullPointerException(NULL_CAUSE_MESSAGE);

    //Thorwable的构造方法中可能会把suppressedExceptions设置为null,当为null时说明不启用异常抑制
    if (suppressedExceptions == null) // Suppressed exceptions not recorded
        return;

    //如果是大小为0的不可变的集合,就为suppressedExceptions重新定义为大小为1的ArrayList,并把被抑制的异常加入
    if (suppressedExceptions == SUPPRESSED_SENTINEL)
        suppressedExceptions = new ArrayList<>(1);

    suppressedExceptions.add(exception);
}

这个用来记录被抑制的异常的suppressedExceptions集合使用起来也很简单,只是单纯的遍历

private void printStackTrace(PrintStreamOrWriter s) {
     
    // Guard against malicious overrides of Throwable.equals by
    // using a Set with identity equality semantics.
    Set<Throwable> dejaVu =
        Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
    dejaVu.add(this);

    synchronized (s.lock()) {
     
        // Print our stack trace
        s.println(this);
        StackTraceElement[] trace = getOurStackTrace();
        for (StackTraceElement traceElement : trace)
            s.println("\tat " + traceElement);

        // Print suppressed exceptions, if any
        //看这里,其实就是遍历打印,getSuppressed():返回所有被try-with-resources语句抑制的异常的数组
        for (Throwable se : getSuppressed())
            se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);

        // Print cause, if any
        Throwable ourCause = getCause();
        if (ourCause != null)
            ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
    }
}

最后我们看一下Spring的bean加载过程中关于异常抑制的地方。在DefaultSingletonBeanRegistry类的getSingleton方法中(只看异常抑制相关的地方)

//用于存储被抑制的异常
@Nullable
private Set<Exception> suppressedExceptions;

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     
    ···
    //判断异常抑制的集合是否为空    
    boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
    //如果为空,声明为新的集合
    if (recordSuppressedExceptions) {
     
        this.suppressedExceptions = new LinkedHashSet<>();
    }

    try {
     
      //方法逻辑
      ···
    } catch (IllegalStateException ex) {
     
       ···
    } catch (BeanCreationException ex) {
     
         //遍历异常抑制集合,调用addRelatedCause方法,addRelatedCause与Throwable中的addSuppressed方法相似,就是判空与加入
        if (recordSuppressedExceptions) {
     
            for (Exception suppressedException : this.suppressedExceptions) {
     
                ex.addRelatedCause(suppressedException);
            }
        }
        //抛出
        throw ex;
    } finally {
     
       ···
    }
 ···
}

你可能感兴趣的:(java,java)