英文:
How to avoid exception shadowing?
问题
在处理异常期间发生异常时,只有最后一个异常会被报告,因为我只能将一个异常添加到Error
对象中。如何在最终错误消息中报告所有异常?
示例:
class main
{
public static void main (String[] args)
{
try {
// Database.insert ();
throw new Exception ("插入失败");
}
catch (Exception ex1) {
try {
// Database.rollback ();
throw new Exception ("回滚失败");
}
catch (Exception ex2) {
throw new Error ("无法回滚事务。", ex2);
}
}
finally {
try {
// Database.close ();
throw new Exception ("关闭失败");
}
catch (Exception ex3) {
throw new Error ("无法关闭数据库。", ex3);
}
}
}
}
在此示例中,一切都失败了。数据库插入引发了 ex1
。回滚引发了 ex2
。关闭数据库引发了 ex3
。当程序被执行时,只有最后一个 ex3
会在 Error
对象中报告。如何也将 ex1
和 ex2
包含在 Error
对象中?Error
的构造函数只接受一个异常。
英文:
When an exception occurs during the handling of an exception, only the last exception gets reported, because I can add just one exception to an Error
object. How to report all exception in the final error message?
Example:
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);
}
}
}
}
In the example everything fails. The database insert causes ex1
. The rollback causes ex2
. And closing the database causes ex3
. When the program gets executed only the last ex3
gets reported in the Error
object. How to include also ex1
and ex2
to the Error
object? The constructor of Error
accepts just one exception.
答案1
得分: 12
我建议您在Java 7中引入的try-with-resource语句与AutoCloseable
接口一起使用。
try (Connection c = DriverManager.getConnection(url)) {
// 进行插入操作
} catch (Exception e) {
throw new Error("插入失败", e);
}
这将适当地关闭您的connection
或通常传递的AutoCloseable
。并且将使用Throwable.addSuppressed()
方法处理异常的屏蔽。
在之前的版本中,等效内容可以在此其他问题中看到。
您的问题还提到了回滚,我还没有涵盖。这可以通过使用前面提到的Throwable.addSuppressed()
方法来完成(正如tobias_k在评论中指出的),尽管老实说,这会变得更加混乱,看起来不太好看:
Exception insertException = null;
try (Connection c = DriverManager.getConnection(url)) {
try {
// 进行插入操作
} catch (Exception e1) {
insertException = e1;
// 进行回滚操作
}
} catch (Exception e2) {
Error error = new Error("插入失败", insertException);
error.addSuppressed(e2);
throw error;
}
只是为了澄清,内部的catch
块只有在插入操作失败时才会被触发。而外部的catch
可以在以下任何一个抛出异常时被触发:
DriverManager.getConnection()
- 您的回滚操作
Connection.close()
您可以访问此链接以查看一个小型演示,演示了堆栈跟踪的外观。
英文:
I suggest you to use try-with-resource-statements introduced in Java 7, in conjunction with the AutoCloseable
-interface.
> Sidenote: The Connection
, Statement
and ResultSet
from java.sql
all implement AutoCloseable
try (Connection c = DriverManager.getConnection(url)) {
// do your inserts
} catch (Exception e) {
throw new Error("Insert failed", e);
}
This will close your connection
or generally the passed AutoCloseable
appropriately. And will handle the shadowing of the exception by using the Throwable.addSuppressed()
method.
What the equivalent looks like in previous versions can be seen on this other question
Your questions also mentiones a rollback which I haven't covered. This can be done by using the before mentioned Throwable.addSuppressed()
method (as pointed out in the comments by tobias_k), though to be honest it gets quite a bit more messy, and doesn't look as nice anymore:
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;
}
Just for clarification, the inner catch
-block, can only be reached when the insert fails. Where as the outer catch
can be reached, when any of the following throws an exception:
DriverManager.getConnection()
- Your rollback
Connection.close()
For a small demo you can visit this link, which illustrates how the stacktrace looks like.
答案2
得分: 2
你所指的在Java中被称为抑制异常。
从Java SE 7开始,有一个try-with-resources
语句,它会自动处理其中抛出的异常。在你的示例中,可以这样使用:
class main
{
public static void main (String[] args)
{
try(Database db = new Database()){ //在关闭资源时可能会抛出ex3
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 ("无法回滚事务。", ex2);
}
}
}
}
}
在这种情况下,如果抛出ex1
和ex3
,你会得到一个带有ex3
的抑制异常列表的ex1
。
如果抛出ex1
、ex2
和ex3
,你会得到ex1
与ex2
的链式异常,以及带有ex3
的抑制异常列表。
英文:
What you refer to is called suppressed exceptions in Java.
Starting from Java SE 7 there is a try-with-resources
statement which automatically handles exceptions thrown within it. In your example it can be used like this:
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);
}
}
}
}
}
In this case, if ex1
and ex3
are thrown, you get ex1
with ex3
in the list of suppressed exceptions.
If ex1
, ex2
and ex3
are thrown, you get ex1
chained with ex2
, and with ex3
in the list of suppressed exceptions.
答案3
得分: 1
我根据Lino的回答更新了我的代码。
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)
英文:
I updated my code based on Lino's 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);
}
}
}
}
Now all four different failures are reported correctly.
- Insert fails
<!-- language: none -->
$ 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)
- Insert and Rollback fail
<!-- language: none -->
$ 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)
- Insert, Rollback and Close fail
<!-- language: none -->
$ 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)
- Close fails
<!-- language: none -->
$ 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
得分: 0
为什么您可能需要这样的结构?一般来说,我首先建议您使用try-with-resources。它将大大简化您的代码。其次,要处理这种情况,您必须设计异常层次结构,这样您就不必在每个地方都使用Exception类,因为这显然是一种反模式,而是会抛出一种新类型的异常,例如,不要抛出新的Exception("关闭失败"),而是定义一个继承异常的CloseFailedException,然后抛出它(throw new CloseFailedException("关闭失败"))。
英文:
Why do you possibly need such a structure? In general, I firstly would suggest you to use try-with-resources. It will simplify your code significantly. Secondly, to handle this case, you have to design exception hierarchy, so you won't be using Exception class everywhere, which is obvious anti-pattern, but will thrown a new type of exceptions, e.g. instead of throwing new Exception ("close failed"), defined CloseFailedException that will inherit exception, and thrown it (throw new CloseFailedException ("close failed")).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论