1. Introduction
1.介绍
It’s pretty common in Java to work with nested exceptions as they can help us track the source of an error.
在Java中,使用嵌套异常是很常见的,因为它们可以帮助我们追踪错误的来源。
When we deal with these kinds of exceptions, sometimes we may want to know the original problem that caused the exception so our application can respond differently for each case. This is especially useful when we work with frameworks that wrap the root exceptions into their own.
当我们处理这类异常时,有时我们可能想知道导致异常的原始问题,这样我们的应用程序就可以对每种情况做出不同的反应。当我们与那些将根部异常包装成自己的框架一起工作时,这尤其有用。
In this short article, we’ll show how to get the root cause exception using plain Java as well as external libraries such as Apache Commons Lang and Google Guava.
在这篇短文中,我们将展示如何使用普通Java以及外部库(如Apache Commons Lang和Google Guava)来获得根本原因的异常。
2. An Age Calculator App
2.一个年龄计算器应用程序
Our application will be an age calculator that tells us how old a person is from a given date received as String in ISO format. We’ll handle 2 possible error cases when parsing the date: a poorly-formatted date and a date in the future.
我们的应用程序将是一个年龄计算器,它告诉我们一个人从一个给定的日期开始,以ISO格式的String收到的年龄。在解析日期时,我们将处理两种可能的错误情况:一种是格式不好的日期,一种是未来的日期。
Let’s first create exceptions for our error cases:
让我们首先为我们的错误案例创建异常。
static class InvalidFormatException extends DateParseException {
InvalidFormatException(String input, Throwable thr) {
super("Invalid date format: " + input, thr);
}
}
static class DateOutOfRangeException extends DateParseException {
DateOutOfRangeException(String date) {
super("Date out of range: " + date);
}
}
Both exceptions inherit from a common parent exception that will make our code a bit clearer:
这两个异常都继承自一个共同的父异常,这将使我们的代码更加清晰。
static class DateParseException extends RuntimeException {
DateParseException(String input) {
super(input);
}
DateParseException(String input, Throwable thr) {
super(input, thr);
}
}
After that, we can implement the AgeCalculator class with a method to parse the date:
之后,我们可以用一个方法实现AgeCalculator类来解析日期。
static class AgeCalculator {
private static LocalDate parseDate(String birthDateAsString) {
LocalDate birthDate;
try {
birthDate = LocalDate.parse(birthDateAsString);
} catch (DateTimeParseException ex) {
throw new InvalidFormatException(birthDateAsString, ex);
}
if (birthDate.isAfter(LocalDate.now())) {
throw new DateOutOfRangeException(birthDateAsString);
}
return birthDate;
}
}
As we can see, when the format is wrong we wrap the DateTimeParseException into our custom InvalidFormatException.
正如我们所看到的,当格式错误时,我们将DateTimeParseException包装成我们自定义的InvalidFormatException.。
Finally, let’s add a public method to our class that receives the date, parses it and then calculates the age:
最后,让我们给我们的类添加一个公共方法,接收日期,解析它,然后计算出年龄。
public static int calculateAge(String birthDate) {
if (birthDate == null || birthDate.isEmpty()) {
throw new IllegalArgumentException();
}
try {
return Period
.between(parseDate(birthDate), LocalDate.now())
.getYears();
} catch (DateParseException ex) {
throw new CalculationException(ex);
}
}
As shown, we’re wrapping the exceptions again. In this case, we wrap them into a CalculationException that we have to create:
如图所示,我们再次包装了这些异常。在这种情况下,我们把它们包装成一个我们必须创建的CalculationException。
static class CalculationException extends RuntimeException {
CalculationException(DateParseException ex) {
super(ex);
}
}
Now, we’re ready to use our calculator by passing it any date in ISO format:
现在,我们准备使用我们的计算器,把任何ISO格式的日期传给它。
AgeCalculator.calculateAge("2019-10-01");
And if the calculation fails, it would be useful to know what the problem was, wouldn’t it? Keep reading to find out how we can do that.
如果计算失败,知道问题出在哪里会很有用,不是吗?继续阅读,了解我们如何做到这一点。
3. Find the Root Cause Using Plain Java
3.使用普通Java查找根源
The first way we’ll use to find the root cause exception is by creating a custom method that loops through all the causes until it reaches the root:
我们将使用的第一种方法是通过创建一个自定义方法,循环浏览所有的原因,直到到达根源,来找到根本原因的异常。
public static Throwable findCauseUsingPlainJava(Throwable throwable) {
Objects.requireNonNull(throwable);
Throwable rootCause = throwable;
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
rootCause = rootCause.getCause();
}
return rootCause;
}
Notice that we’ve added an extra condition in our loop to avoid infinite loops when handling recursive causes.
请注意,我们在循环中增加了一个额外的条件,以避免在处理递归原因时出现无限循环。
If we pass an invalid format to our AgeCalculator, we’ll get the DateTimeParseException as the root cause:
如果我们向AgeCalculator传递一个无效的格式,我们会得到DateTimeParseException作为根本原因。
try {
AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
assertTrue(findCauseUsingPlainJava(ex) instanceof DateTimeParseException);
}
However, if we use a future date we’ll get a DateOutOfRangeException:
然而,如果我们使用一个未来的日期,我们会得到一个DateOutOfRangeException。
try {
AgeCalculator.calculateAge("2020-04-04");
} catch (CalculationException ex) {
assertTrue(findCauseUsingPlainJava(ex) instanceof DateOutOfRangeException);
}
Furthermore, our method also works for non-nested exceptions:
此外,我们的方法也适用于非嵌套异常。
try {
AgeCalculator.calculateAge(null);
} catch (Exception ex) {
assertTrue(findCauseUsingPlainJava(ex) instanceof IllegalArgumentException);
}
In this case, we get an IllegalArgumentException since we passed in null.
在这种情况下,我们得到一个IllegalArgumentException,因为我们传入了null。
4. Find the Root Cause Using Apache Commons Lang
4.使用Apache Commons Lang找到根本原因
We’ll now demonstrate finding the root cause using third-party libraries instead of writing our custom implementation.
现在我们将演示使用第三方库而不是编写我们的自定义实现来寻找根源。
Apache Commons Lang provides an ExceptionUtils class which provides some utility methods to work with exceptions.
Apache Commons Lang提供了一个ExceptionUtils类,提供了一些处理异常的实用方法。
We’ll use the getRootCause() method with our previous example:
我们将使用getRootCause()方法与我们之前的例子。
try {
AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
assertTrue(ExceptionUtils.getRootCause(ex) instanceof DateTimeParseException);
}
We get the same root cause as before. The same behavior applies to the other examples that we’ve listed above.
我们得到的根本原因与之前一样。同样的行为也适用于我们上面列出的其他例子。
5. Find the Root Cause Using Guava
5.使用番石榴找到根源
The last way we’re going to try is by using Guava. Similar to Apache Commons Lang, it provides a Throwables class with a getRootCause() utility method.
我们要尝试的最后一种方式是通过使用Guava。与Apache Commons Lang类似,它提供了一个Throwables类,有一个getRootCause() 实用方法。
Let’s try it out with the same example:
让我们用同样的例子来试一下。
try {
AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
assertTrue(Throwables.getRootCause(ex) instanceof DateTimeParseException);
}
The behavior is exactly the same as with the other methods.
其行为与其他方法完全相同。
6. Conclusion
6.结论
In this article, we’ve demonstrated how to use nested exceptions in our application and implemented a utility method to find the root cause exception.
在这篇文章中,我们已经演示了如何在我们的应用程序中使用嵌套的异常,并实现了一个实用的方法来查找异常的根源。
We’ve also shown how to do the same by using third-party libraries like Apache Commons Lang and Google Guava.
我们还展示了如何通过使用第三方库,如Apache Commons Lang和Google Guava,来实现同样的目的。
As always, the full source code for the examples is available over on GitHub.
一如既往,这些示例的完整源代码可在GitHub上获取。