【问题标题】:Best way to check whether a certain exception type was the cause (of a cause, etc ...) in a nested exception?检查某个异常类型是否是嵌套异常中的原因(原因等)的最佳方法?
【发布时间】:2023-03-25 07:30:01
【问题描述】:

我正在编写一些 JUnit 测试来验证是否抛出了 MyCustomException 类型的异常。但是,此异常多次包含在其他异常中,例如在 InvocationTargetException 中,而后者又被包装在 RuntimeException 中。

确定 MyCustomException 是否以某种方式导致我实际捕获的异常的最佳方法是什么?我想做这样的事情(见下划线):


try {
    doSomethingPotentiallyExceptional();
    fail("Expected an exception.");
} catch (RuntimeException e) {
     if (!e.wasCausedBy(MyCustomException.class)
        fail("Expected a different kind of exception.");
}

我想避免调用getCause() 一些“层”深,以及类似的丑陋解决方法。有更好的方法吗?

(显然,Spring 有 NestedRuntimeException.contains(Class),它可以满足我的要求 - 但我没有使用 Spring。)

关闭: 好的,我想确实没有绕过实用方法 :-) 感谢所有回复的人!

【问题讨论】:

    标签: java spring exception exception-handling nested-exceptions


    【解决方案1】:

    如果您使用的是Apache Commons Lang,那么您可以使用以下内容:

    (1) 原因应完全属于指定类型时

    if (ExceptionUtils.indexOfThrowable(exception, ExpectedException.class) != -1) {
        // exception is or has a cause of type ExpectedException.class
    }
    

    (2) 当原因应该是指定类型或其子类类型时

    if (ExceptionUtils.indexOfType(exception, ExpectedException.class) != -1) {
        // exception is or has a cause of type ExpectedException.class or its subclass
    }
    

    【讨论】:

    • 那我们怎样才能返回我们预期的原因异常呢?
    • ExceptionUtils.hasCause(e, ExpectedException.class)
    【解决方案2】:

    为什么要避免使用getCause。当然,您可以为自己编写一个执行任务的方法,例如:

    public static boolean isCause(
        Class<? extends Throwable> expected,
        Throwable exc
    ) {
       return expected.isInstance(exc) || (
           exc != null && isCause(expected, exc.getCause())
       );
    }
    

    【讨论】:

    • 请注意,如果原因有循环,此算法可能会导致无限循环,这在某些情况下可能会发生,例如 EclipseLink DB 异常。 Apache Commons Lang ExceptionUtils::getRootCause 处理这种情况,所以帕特里克的回答中的 indexOfThrowable 可能也可以。
    • @DavidS 不是无限循环 - 它会快速抛出 StackOverflowError。如果你有这样的因果关系崩溃,那么你就有更大的问题(可能试图重用异常对象,即使它们奇怪地可变)。
    • 然后是 StackOverflowError,谢谢。无论如何,这不是我编写的代码的问题。这是一些常见库中的问题,包括一些 Oracle jdbc 驱动程序。这是一个很常见的问题,Apache Commons 选择在getRootCause 处理它,Oracle 选择在printStackTrace 处理它,Guava 考虑在Throwables 处理它。我的评论旨在警告这一点,而不是建议如何最好地构造异常。
    • @DavidS 我很失望图书馆附带预定悖论。有趣的 OpenJDK printStackTace 来源提出了关于防范恶意异常的建议,但不止一种方式失败。 P4 错误 - 我更担心库而不是尝试处理所有可能的 DoS 错误。易受此类影响的代码无处不在 - 例如使用 equals
    • 我还在纯 JDK NoClassDefFound 错误中看到了无限循环(由在静态初始化块期间触发的另一个异常引起。)
    【解决方案3】:

    我认为您别无选择,只能通过 getCause 层调用。如果您查看您提到的 Spring NestedRuntimeException 的源代码,那就是它的实现方式。

    【讨论】:

      【解决方案4】:

      模仿是最真诚的奉承。 Based on a quick inspection of the source,这正是 NestedRuntimeException 所做的:

      /**
       * Check whether this exception contains an exception of the given type:
       * either it is of the given class itself or it contains a nested cause
       * of the given type.
       * @param exType the exception type to look for
       * @return whether there is a nested exception of the specified type
       */
      public boolean contains(Class exType) {
          if (exType == null) {
              return false;
          }
          if (exType.isInstance(this)) {
              return true;
          }
          Throwable cause = getCause();
          if (cause == this) {
              return false;
          }
          if (cause instanceof NestedRuntimeException) {
              return ((NestedRuntimeException) cause).contains(exType);
          }
          else {
              while (cause != null) {
                  if (exType.isInstance(cause)) {
                      return true;
                  }
                  if (cause.getCause() == cause) {
                      break;
                  }
                  cause = cause.getCause();
              }
              return false;
          }
      }
      

      CAVEAT:以上是截至 2009 年 3 月 4 日的代码,因此,如果您真的想知道 Spring 现在在做什么,您应该研究当前存在的代码(无论何时)。

      【讨论】:

        【解决方案5】:

        好吧,我认为如果不致电getCause(),就无法做到这一点。如果您认为这样做很难实现 utility 类:

        public class ExceptionUtils {
             public static boolean wasCausedBy(Throwable e, Class<? extends Throwable>) {
                 // call getCause() until it returns null or finds the exception
             }
        }
        

        【讨论】:

          【解决方案6】:

          你可以使用番石榴做到这一点:

          FluentIterable.from(Throwables.getCausalChain(e))
                                  .filter(Predicates.instanceOf(ConstraintViolationException.class))
                                  .first()
                                  .isPresent();
          

          【讨论】:

          • 我认为这在某些情况下也可能导致无限循环,就像其他一些答案一样。 (例如,我在 getCause() 中看到过这样的链,用于 NoClassDefFound 异常。)为了避免需要像 @BobCross 的 Spring 的答案中的 cause.getCause() == cause 这样的检查。
          • @JoshuaGoldberg 根据guava#2866,自 2017 年发布的 23.0 以来,这对无限循环是安全的。更具体地说,在这种情况下,它会抛出一个IllegalArgumentException
          • @M.Justin,当链是递归的并且 getCausalChain 抛出 IllegalArgumentException 时,这是否会阻止它用于检查原因链中的特定异常类型?
          • 正确,如果存在循环,则无法使用此方法检查特定异常。它在这种情况下所做的是保护您免受由于无限循环而导致的代码阻塞。
          【解决方案7】:

          根据 Patrick Boos 的回答: 如果您使用 Apache Commons Lang 3,您可以检查:

          indexOfThrowable:返回异常链中与指定类(exactly)匹配的第一个 Throwable 的(从零开始的)索引。 指定类的子类不匹配

          if (ExceptionUtils.indexOfThrowable(e, clazz) != -1) {
              // your code
          }
          

          indexOfType:返回与异常链中指定的类或子类匹配的第一个 Throwable 的(从零开始的)索引。 指定类的子类匹配

          if (ExceptionUtils.indexOfType(e, clazz) != -1) {
              // your code
          }
          

          Java 8 的多种类型示例:

          Class<? extends Throwable>[] classes = {...}
          boolean match = Arrays.stream(classes)
                      .anyMatch(clazz -> ExceptionUtils.indexOfType(e, clazz) != -1);
          

          【讨论】:

            【解决方案8】:

            您可以使用ExceptionUtils.hasCause(ex, type)

            【讨论】:

            • 请注意,hasCause 在 apache 的 ExceptionUtils 中(还有一些其他包带有 ExceptionUtils 类。)
            【解决方案9】:

            如果感兴趣的异常绝对是“根本”原因,assertj 是查找getRootCause 检查的另一个地方,尽管从今天的源头来看,它似乎在其他答案中讨论了可能的无限循环问题.

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2014-06-03
              • 2011-10-15
              • 1970-01-01
              • 2017-02-13
              • 2022-08-22
              • 1970-01-01
              • 2013-05-06
              相关资源
              最近更新 更多