如何避免异常遮盖?

huangapple go评论83阅读模式
英文:

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 对象中报告。如何也将 ex1ex2 包含在 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);
        }
      }
    }
  }
}

在这种情况下,如果抛出ex1ex3,你会得到一个带有ex3的抑制异常列表的ex1

如果抛出ex1ex2ex3,你会得到ex1ex2链式异常,以及带有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);
      }
    }
  }
}

现在所有四种不同的失败情况都得到了正确的报告。

  1. 插入失败
$ 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)
  1. 插入和回滚失败
$ 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)
  1. 插入、回滚和关闭失败
$ 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)
  1. 关闭失败
$ 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.

  1. Insert fails
    <!-- language: none -->
$ java -cp . main fail ok ok
Exception in thread &quot;main&quot; 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)
  1. Insert and Rollback fail
    <!-- language: none -->
$ java -cp . main fail fail ok
Exception in thread &quot;main&quot; 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)
  1. Insert, Rollback and Close fail
    <!-- language: none -->
$ java -cp . main fail fail fail
Exception in thread &quot;main&quot; 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)
  1. Close fails
    <!-- language: none -->
$ java -cp . main ok ok fail
Exception in thread &quot;main&quot; 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")).

huangapple
  • 本文由 发表于 2020年8月27日 16:08:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/63611752.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定