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 方法好,但至少我们知道实际发生了什么,没有丢失任何东西。
我正在考虑 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 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 方法好,但至少我们知道实际发生了什么,没有丢失任何东西。