简体   繁体   English

如何避免异常阴影?

[英]How to avoid exception shadowing?

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.当在处理异常的过程中发生异常时,只会报告最后一个异常,因为我只能向Error对象添加一个异常。 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 .数据库插入导致ex1 The rollback causes ex2 .回滚导致ex2 And closing the database causes ex3 .关闭数据库会导致ex3 When the program gets executed only the last ex3 gets reported in the Error object.当程序执行时,只有最后一个ex3会在Error对象中报告。 How to include also ex1 and ex2 to the Error object?如何将ex1ex2也包含到Error对象中? The constructor of Error accepts just one exception. Error的构造函数只接受一个异常。

I suggest you to use try-with-resource -statements introduced in Java 7, in conjunction with the AutoCloseable -interface.我建议您将 Java 7 中引入的try-with-resource语句与AutoCloseable结合使用。

Sidenote : The Connection , Statement and ResultSet from java.sql all implement AutoCloseable旁注java.sqlConnectionStatementResultSet都实现了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.这将适当地关闭您的connection或通常通过的AutoCloseable And will handle the shadowing of the exception by using the Throwable.addSuppressed() method.并且将使用Throwable.addSuppressed()方法处理异常的阴影。

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:这可以通过使用前面提到的Throwable.addSuppressed()方法(如 tobias_k 的评论中所指出的)来完成,但老实说它变得更加混乱,并且看起来不再那么好:

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.为了澄清起见,只有在插入失败时才能到达内部catch块。 Where as the outer catch can be reached, when any of the following throws an exception:当以下任何一个抛出异常时,可以到达外部catch

  • DriverManager.getConnection()
  • Your rollback你的回滚
  • Connection.close()

For a small demo you can visit this link , which illustrates how the stacktrace looks like.对于一个小演示,您可以访问此链接,它说明了堆栈跟踪的样子。

What you refer to is called suppressed exceptions in Java.您所指的在 Java 中称为抑制异常

Starting from Java SE 7 there is a try-with-resources statement which automatically handles exceptions thrown within it.从 Java SE 7 开始,有一个try-with-resources 语句,可以自动处理其中抛出的异常。 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.在这种情况下,如果抛出ex1ex3 ,您将在抑制异常列表中获得ex1ex3

If ex1 , ex2 and ex3 are thrown, you get ex1 chained with ex2 , and with ex3 in the list of suppressed exceptions.如果抛出ex1ex2ex3 ,您将得到ex1ex2以及ex3 链接在抑制异常列表中。

I updated my code based on Lino's answer .我根据 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);
      }
    }
  }
}

Now all four different failures are reported correctly.现在所有四种不同的故障都正确报告。

  1. Insert fails插入失败
$ 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. Insert and Rollback fail插入和回滚失败
$ 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. Insert, Rollback and Close fail插入、回滚和关闭失败
$ 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. Close fails关闭失败
$ 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)

Why do you possibly need such a structure?为什么你可能需要这样的结构? In general, I firstly would suggest you to use try-with-resources.一般来说,我首先建议你使用 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, eg instead of throwing new Exception ("close failed"), defined CloseFailedException that will inherit exception, and thrown it (throw new CloseFailedException ("close failed")).其次,为了处理这种情况,你必须设计异常层次结构,所以你不会到处使用Exception类,这是明显的反模式,但会抛出一种新类型的异常,例如而不是抛出新的异常(“关闭failed"),定义了将继承异常的 CloseFailedException,并抛出它(throw new CloseFailedException ("close failed"))。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM