Java - 检测在 `finally` 块期间是否有正在进行的异常

Java - detect whether there is an exception in progress during `finally` block

我正在考虑 Java 语言规范:

If the catch block completes abruptly for reason R, then the finally block is executed. Then there is a choice:

  • If the finally block completes normally, then the try statement completes abruptly for reason R.

  • If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and reason R is discarded).

我有一个块如下:

try {
  .. do stuff that might throw RuntimeException ...
} finally {
  try {
    .. finally block stuff that might throw RuntimeException ...
  } catch {
    // what to do here???
  }
}

理想情况下,我希望 finally 块中抛出的任何 RuntimeException 都逃脱, 仅当它不会导致主 RuntimeException 抛出时=15=] 块被丢弃.

在Java中有什么方法可以让我知道与finally块关联的块是否正常完成?

我想我可以将 boolean 设置为主要 try 块的最后一个语句(例如,completedNormally = true。这是最好的方法吗?有更好/更标准的东西吗?

我认为这个问题没有 惯用的解决方案,部分原因是您通常使用 finally 来清理资源,而完全不考虑代码分配的资源是否正常终止。

例如,您最终关闭了一个连接,但是交易回滚在 catch 块中或作为包含在 try 中的代码块的最后一条语句提交。

关于在 finally 块内抛出的 throwable,您应该决定将哪个异常传递给调用者是最重要的。您最终可以创建自己的异常,它包含对这两个异常的引用,在这种情况下,您需要声明一个在 try 外部初始化并在 catch 内部设置的变量。

例如,在下面的代码中,您要么正常完成,要么抛出异常,同时尝试恢复(回滚事务)并最终尝试清理。

两者都可能失败,并且您将您认为最重要的数据包装在您 最终 抛出的异常中。

    private void foo() throws SQLException {

        Throwable firstCause = null;
        try {
            conn.prepareStatement("...");
            // ...

            conn.commit();
        } catch (SQLException e) {
            firstCause = e;
            conn.rollback();
            throw e;
        } finally {
            try {
                conn.close();
            } catch (Exception e) {
                throw new RuntimeException(firstCause);
                // or 
                // throw new RuntimeException(e);
                // or
                // throw new MyException(e,firstCause);
            }
        }
    }

您可以捕获原始异常并从 finally 块中重新抛出它。

下面的代码就是这样做的,下面的方法抛出的异常将具有堆栈跟踪和由外部 RuntimeException.

指示的原因
private void testException() {
    RuntimeException originalFailure = null;
    try {
        throw new RuntimeException("Main exception");
    } catch (RuntimeException e) {
        originalFailure = e;
    } finally {
        try {
            throw new RuntimeException("Final exception");
        } catch (RuntimeException e) {
          if (originalFailure != null) {
              throw originalFailure;
          } else {
              throw e;  //OR do nothing
          }
        }
    }

}

我假设您的 finally 块正在清理。完成此类清理的一个好方法是创建一个实现 AutoCloseable, so your code can place it in a try-with-resources statement:

的 class
class DoStuff
implements AutoCloseable {
    public void doStuffThatMightThrowException() {
        // ...
    }

    @Override
    public void close() {
        // do cleanup
    }
}

(注意它不需要是 public class。事实上,它可能不应该是。)

您的示例中的代码将如下所示:

try (DoStuff d = new DoStuff()) {
    d.doStuffThatMightThrowException();
}

至于清理时抛出异常怎么办:变成了suppressed exception。它不会显示在堆栈跟踪中,但如果您真的想要(您可能不会)可以访问它。

我认为关键是不要失去最初的原因。

如果我们看看 try-with-resources 的行为:

private static class SomeAutoCloseableThing implements AutoCloseable {

    @Override
    public void close() {
        throw new IllegalStateException("closing failed");
    }

}

public static void main(String[] args) {
    try (SomeAutoCloseableThing thing = new SomeAutoCloseableThing()) {
        throw new IllegalStateException("running failed");
    }
}

我们最终得到:

Exception in thread "main" java.lang.IllegalStateException: running failed
    at Main.main(Main.java:16)
    Suppressed: java.lang.IllegalStateException: closing failed
        at Main$SomeAutoCloseableThing.close(Main.java:9)
        at Main.main(Main.java:17)

这个堆栈跟踪很棒,因为我们看到了两个异常,即我们没有丢失 running failed 一个。


在没有 try-with-resources 的情况下实现这个,错误的 方式:

public static void main(String[] args) {
    SomeAutoCloseableThing thing = new SomeAutoCloseableThing();
    try {
        throw new IllegalStateException("running failed");
    } finally {
        thing.close();
    }
}

我们最终得到:

Exception in thread "main" java.lang.IllegalStateException: closing failed
    at Main$SomeAutoCloseableThing.close(Main.java:9)
    at Main.main(Main.java:19)

我们不知道 running failed 也发生了,因为我们破坏了控制流,如果您需要调试这种情况,那就太糟糕了。


在没有 try-with-resources 的情况下实现这个,正确的 方式(在我看来)是 "log and forget" finally 中发生的异常]块:

public static void main(String[] args) {
    SomeAutoCloseableThing thing = new SomeAutoCloseableThing();
    try {
        throw new IllegalStateException("running failed");
    } finally {
        try {
            thing.close();
        } catch (Exception e) {
            LoggerFactory.getLogger(Main.class).error("An error occurred while closing SomeAutoCloseableThing", e);
        }
    }
}

我们最终得到:

17:10:20.030 [main] ERROR Main - An error occurred while closing SomeAutoCloseableThing
java.lang.IllegalStateException: closing failed
    at Main$SomeAutoCloseableThing.close(Main.java:10) ~[classes/:?]
    at Main.main(Main.java:21) [classes/:?]
Exception in thread "main" java.lang.IllegalStateException: running failed
    at Main.main(Main.java:18)

不如 try-with-resources 方法好,但至少我们知道实际发生了什么,没有丢失任何东西。