addSuppressed异常抑制

Posted 戚焱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 
       ···
    
 ···

以上是关于addSuppressed异常抑制的主要内容,如果未能解决你的问题,请参考以下文章

抑制异常和原因之间的区别

我们在 Scala 中有抑制异常吗?

华为交换机系列异常流量抑制

Java 7有啥新特性?

如何抑制 yfinance 的异常?

抑制预期 Oracle 异常的 PHP 警告