如何避免异常阴影?
Posted
技术标签:
【中文标题】如何避免异常阴影?【英文标题】:How to avoid exception shadowing? 【发布时间】:2020-08-27 08:08:22 【问题描述】:在处理异常的过程中发生异常时,只报告最后一个异常,因为我可以将一个异常添加到Error
对象。如何在最终的错误信息中报告所有异常?
例子:
class main
public static void main (String[] args)
try
// Database.insert ();
throw new Exception ("insert failed");
catch (Exception ex1)
try
// Database.rollback ();
throw new Exception ("rollback failed");
catch (Exception ex2)
throw new Error ("Can not roll back transaction.", ex2);
finally
try
// Database.close ();
throw new Exception ("close failed");
catch (Exception ex3)
throw new Error ("Can not close database.", ex3);
在示例中,一切都失败了。数据库插入导致ex1
。回滚导致ex2
。关闭数据库会导致ex3
。当程序执行时,只有最后一个 ex3
会在 Error
对象中报告。如何将ex1
和ex2
也包含到Error
对象中? Error
的构造函数只接受一个异常。
【问题讨论】:
您可以创建包含异常列表的自定义异常类吗?并继续更新并重新抛出? 【参考方案1】:我建议您将 Java 7 中引入的 try-with-resource-statements 与 AutoCloseable
-interface 结合使用。
旁注:
java.sql
中的Connection
、Statement
和ResultSet
都实现了AutoCloseable
try (Connection c = DriverManager.getConnection(url))
// do your inserts
catch (Exception e)
throw new Error("Insert failed", e);
这将适当地关闭您的connection
或通常通过的AutoCloseable
。并将使用Throwable.addSuppressed()
方法处理异常的阴影。
可以在this other question上看到之前版本中的等效外观
您的问题还提到了我没有涉及的回滚。这可以通过使用前面提到的 Throwable.addSuppressed()
方法(如 tobias_k 的 pointed out in the comments)来完成,但老实说它变得有点混乱,而且看起来不再那么漂亮了:
Exception insertException = null;
try (Connection c = DriverManager.getConnection(url))
try
// do your inserts
catch (Exception e1)
insertException = e1;
// do your rollback
catch (Exception e2)
Error error = new Error("Insert failed", insertException);
error.addSuppressed(e2);
throw error;
只是为了澄清,内部catch
-block,只有在插入失败时才能到达。当外部的catch
可以到达时,当任何 抛出异常时:
DriverManager.getConnection()
您的回滚
Connection.close()
对于小型演示,您可以访问此link,它说明了堆栈跟踪的外观。
【讨论】:
恕我直言,更一般的答案是使用addSuppressed
添加“阴影”异常,并且在许多情况下可以使用 AutoCloseable 来为您完成所有这些工作。
@tobias_k 如何在我的代码中使用addSuppressed
?当我处理异常时,我不知道它是否会被稍后发生的异常抑制。
如何知道AutoCloseable代码中的异常处理是如何工作的?以及如何确定较早的异常确实被添加到抑制异常列表中?
@Lino 我不明白为什么“回滚”会抑制“关闭”。对我来说,两种报告替代方案似乎是可行的:要么报告第一个压制后者,要么报告最后压制前者。但是报告中间的那个会抑制第一个和最后一个看起来有点混乱。
@ceving 你是对的,我已经改变了压制的顺序。现在看起来像 this【参考方案2】:
您所指的在Java中称为suppressed exceptions。
从 Java SE 7 开始,有一个 try-with-resources
statement 可以自动处理其中抛出的异常。在您的示例中,它可以这样使用:
class main
public static void main (String[] args)
try(Database db = new Database()) //ex3 can be thrown during closing the resource
try
// db.insert ();
throw new Exception ("insert failed");
catch (Exception ex1)
try
// db.rollback ();
throw new Exception ("rollback failed");
catch (Exception ex2)
throw new Error ("Can not roll back transaction.", ex2);
在这种情况下,如果ex1
和ex3
被抛出,您会在抑制的异常列表中得到ex1
和ex3
。
如果ex1
、ex2
和ex3
被抛出,你会得到ex1
chained 和ex2
和ex3
在抑制的异常列表中。
【讨论】:
【参考方案3】:我根据 Lino 的 answer 更新了我的代码。
class main
public static void main (String[] args)
Throwable suppressed = null;
try
// Database.insert ();
if ("fail".equals(args[0]))
throw new Exception ("insert failed");
catch (Exception ex1)
suppressed = ex1;
try
// Database.rollback ();
if ("fail".equals(args[1]))
throw new Exception ("rollback failed");
throw new Error ("Can not insert into database.", ex1);
catch (Exception ex2)
ex2.addSuppressed (suppressed);
suppressed = ex2;
throw new Error ("Can not roll back transaction.", ex2);
finally
try
// Database.close ();
if ("fail".equals(args[2]))
throw new Exception ("close failed");
catch (Exception ex3)
if (suppressed != null)
ex3.addSuppressed (suppressed);
throw new Error ("Can not close database.", ex3);
现在所有四种不同的故障都已正确报告。
-
插入失败
$ java -cp . main fail ok ok
Exception in thread "main" java.lang.Error: Can not insert into database.
at main.main(main.java:18)
Caused by: java.lang.Exception: insert failed
at main.main(main.java:10)
-
插入和回滚失败
$ java -cp . main fail fail ok
Exception in thread "main" java.lang.Error: Can not roll back transaction.
at main.main(main.java:23)
Caused by: java.lang.Exception: rollback failed
at main.main(main.java:17)
Suppressed: java.lang.Exception: insert failed
at main.main(main.java:10)
-
插入、回滚和关闭失败
$ java -cp . main fail fail fail
Exception in thread "main" java.lang.Error: Can not close database.
at main.main(main.java:35)
Caused by: java.lang.Exception: close failed
at main.main(main.java:30)
Suppressed: java.lang.Exception: rollback failed
at main.main(main.java:17)
Suppressed: java.lang.Exception: insert failed
at main.main(main.java:10)
-
关闭失败
$ java -cp . main ok ok fail
Exception in thread "main" java.lang.Error: Can not close database.
at main.main(main.java:35)
Caused by: java.lang.Exception: close failed
at main.main(main.java:30)
【讨论】:
【参考方案4】:为什么您可能需要这样的结构?一般来说,我首先建议您使用 try-with-resources。它将显着简化您的代码。其次,要处理这种情况,您必须设计异常层次结构,因此您不会到处使用 Exception 类,这显然是反模式,但会抛出一种新类型的异常,例如而不是抛出新的异常(“关闭失败”),定义了将继承异常的CloseFailedException,并抛出它(抛出新的CloseFailedException(“关闭失败”))。
【讨论】:
我写了一个minimal reproducible example,为了尽量减少它,我避免写一堆异常类。我也没有在数据库中插入任何东西。以上是关于如何避免异常阴影?的主要内容,如果未能解决你的问题,请参考以下文章