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

5
假设我正在处理`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)构造函数,那么我可以遵循一种相当粗糙的“手动克隆”过程来实现我的目标:
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具有错误的类型,因此可能无法被更高层框架正确处理。

在处理这种情况时是否有最佳做法?


当使用Foo原因创建Bar异常时,Foo异常的堆栈跟踪是否会丢失?或者你可以像这样做bar.getCause().getStackTrace()吗? - Kasper van den Berg
@KaspervandenBerg 我已经编辑了我的问题,展示了 FooException 如何丢失。这与创建带有原因的异常无关,而是由方法 doSomethingElse 抛出的异常。 - Adam Burley
2个回答

4

不错的观点(我没有注意到这个方法,因为我只寻找了一个“setCause”方法)......只要BarException还没有定义原因。然而,我仍然没有找到任何异常类(至少在java规范内),它定义了原因,但缺少(message, cause)构造函数。 - Adam Burley

1
只要将原始异常作为参数传递给新异常,就可以创建“由...引起”的链,并保留堆栈跟踪。您的用例似乎有些奇怪。对我来说,在恢复或处理错误时,如果仅使用某些日志记录,则异常是另一个错误,而不是真正的“由...引起”的其他错误。我只会记录“foo”并抛出“bar”。
在某些情况下,我想你的方式可能是有意义的。为此,您可以将“foo”传递为doSomethingElse(foo),并在处理此过程中出现问题时抛出新的BarException(foo)。几乎所有标准异常都支持此构造函数,如果需要自己创建,请创建一个委托到这些构造函数的构造函数。
我个人不会像您一样使用它们。如果我希望以某种原因抛出不同类型的异常,则使用它们。例如,将特定类型的异常转换为我的应用程序异常,或在有意义的情况下将已检查的异常转换为未检查的异常。在这种情况下,仍然保留原始异常和完整的“由...引起”的跟踪。

“我只是记录一下foo然后抛出bar”:记录异常然后将其丢弃不是一个好的做法。如果另一个库依赖于您的代码并使用非标准日志记录器(例如JMS日志记录),会怎样呢?“几乎所有标准异常都支持这个构造函数”并非如此,ClassCastException就是其中之一。 - Adam Burley
我个人不会像你所认为的那样使用它们。我使用它们来抛出与我捕获的异常类型不同的异常。你怎么会觉得我不是在尝试这样做呢?例如,当尝试抛出与我捕获的异常类型不同的异常时,仍然可能发生ClassCastException等异常。 - Adam Burley
要做到这一点,你可以将foo作为参数传递给doSomethingElse(foo)。正如我在问题中所述,doSomethingElse不是由我编写的类。它不支持将异常作为参数传递。 - Adam Burley
你是否经常需要抛出一个带有其他异常作为原因的ClassCastException?或者你真的有一个现实的例子吗?我并不希望你在记录日志时忽略异常,只是如果在获取所有数据的副本时出现问题并且你需要另一种类型,你可以将其记录下来并抛出你能够处理的部分。堆栈跟踪最可能是供人类消费的,所以你同样可以在日志中找到它。在你的“bar2”示例中,你可以省略try-catch,因为你想要抛出完全相同的东西。 - kg_sYy
你应该知道“高级框架”正确的类型,并进行管理。这不应该是一个问题。 - kg_sYy
是的,我想抛出相同类型但原因不同的异常。而且,我确实需要抛出一个带有另一个异常作为原因的ClassCastException,否则该异常将从堆栈跟踪中被擦除。 - Adam Burley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接