如何为处理其他异常时抛出的异常获取正确链接的堆栈跟踪?

How to get a correctly-chained stack trace for exceptions thrown when handling other exceptions?

假设我正在处理 FooException 并且 BarException 发生了。假设它们都是未经检查的异常。

我想在堆栈跟踪中看到的是:

com.bar.BarException: Bar Message
    at com.baz.BazCode(BazCode.java:123)
    ...
Caused by: com.foo.FooException: Foo Message
    at com.baz.BazCode(BazCode.java:321)
    ....
Caused by: ...

然而,默认情况下,FooException 的所有记录将从堆栈跟踪中删除。例如:

// In a class written by me
/**
  * ...
  * @throws FooException if foo happens
  * @throws BarException if bar happens
  */
public void upperFrame() {
    try {
        foo.doSomething();
    } catch (FooException foo) {
        bar.doSomethingElse();
    }
}

// In class Bar (not written by me)
public void doSomethingElse() {
    if (someConditionWhichHappensToBeTrueInThisScenario()) {
        throw new BarException("Hello Bar World"); // At this point, FooException gets erased from the stack trace
    }
}

如果 BarException 有一个 (message, cause) 构造函数,那么我可以遵循一种相当粗略的 "manual cloning" 过程来实现我的目标:

try {
    foo.doSomething();
} catch (FooException foo) {
    try {
        bar.doSomethingElse();
    } catch (BarException bar) {
        BarException bar2 = new BarException(bar.getMessage(), foo);
        bar2.setStackTrace(bar.getStackTrace());
        throw bar2;
    }
}

但是,如果 BarException 没有这样的构造函数(例如 ClassCastException),那么我只能做这样的事情:

try {
    foo.doSomething();
} catch (FooException foo) {
    try {
        bar.doSomethingElse();
    } catch (BarException bar) {
        RuntimeException e = new RuntimeException("com.bar.BarException: " + bar.getMessage(), foo);
        e.setStackTrace(bar.getStackTrace());
        throw e;
    }
}

这很危险,因为 e 类型错误,因此可能无法被更高的框架正确处理。

有"best-practice"处理这种情况的方法吗?

一种解决方案是使用 Throwable#initCause(Throwable) 方法:

bar.initCause(foo);

只要您将原始异常作为参数传递给新异常,它就会创建 "caused by" 链并保留堆栈跟踪。您的用例似乎有点奇怪。对我来说,使用多个日志记录恢复或处理错误时的异常是另一个错误,而不是 "caused by" 另一个错误。我只会记录 "foo" 并抛出 "bar".

在某些情况下,我想您的方式可能有意义。为此,您可以将 "foo" 作为 doSomethingElse(foo) 传递,并在处理此问题时抛出 new BarException(foo) 。几乎所有标准异常都支持此构造函数,如果您需要自己创建一个构造函数来委托给这些构造函数。

我个人不会像您看起来那样使用它们。如果出于某种原因我想要不同的类型,我会使用它们来抛出与我捕获的不同类型的异常。例如,将特定类型的异常转换为我的应用程序,或者在有意义的情况下将已检查转换为未检查。在这种情况下,保留原始异常和完整的 "caused by.." 跟踪仍然很好..