如何为处理其他异常时抛出的异常获取正确链接的堆栈跟踪?
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.." 跟踪仍然很好..
假设我正在处理 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.." 跟踪仍然很好..