【问题标题】:rethrow java exception with new message, preserving the exception type if it is in the method declaration list用新消息重新抛出 java 异常,如果它在方法声明列表中,则保留异常类型
【发布时间】:2014-07-10 23:38:01
【问题描述】:

我正在尝试创建一个帮助方法来消除对这样的代码的需要:

void foo() throws ExceptionA, ExceptionB, DefaultException {
  try {
     doSomething(); // that throws ExceptionA, ExceptionB or others
  } catch (Exception e) {
    if (e instanceof ExceptionA)
        throw new ExceptionA("extra message", e);
    if (e instanceof ExceptionB)
        throw new ExceptionB("extra message", e);

    throw new DefaultException("extra message", e);
  }
}

问题是我需要同时在函数声明和函数体中维护 throws 列表。我正在寻找如何避免这种情况并使更改抛出列表足够,并且我的代码看起来像:

void foo() throws ExceptionA, ExceptionB, DefaultException {
  try {
     doSomething(); // that throws ExceptionA, ExceptionB or others
  } catch (Exception e) {
    rethrow(DefaultException.class, "extra message", e);
  }
}

哪里 rethrow 方法足够聪明,可以从方法声明中识别 throws 列表。

这样,当我更改我的方法在 throws 列表中传播的类型列表时,我不需要更改正文。

以下是可以解决问题的函数。问题是因为它不知道它会抛出什么类型的异常,它的 throws 声明必须说 Exception,但如果它这样做了,那么将要使用它的方法也需要指定它,以及整个想法使用 throws 列表会下地狱。

有什么建议可以解决这个问题吗?

@SuppressWarnings("unchecked")
public static void rethrow(Class<?> defaultException, String message, Exception e) throws Exception
{
  final StackTraceElement[] ste = Thread.currentThread().getStackTrace();

  final StackTraceElement element = ste[ste.length - 1 - 1];

  Method method = null;

  try {
     method = getMethod(element);
  } catch (ClassNotFoundException ignore) {
     // ignore the Class not found exception - just make sure the method is null
     method = null;
  }

  boolean preserveType = true;

  if (method != null) {

     // if we obtained the method successfully - preserve the type 
     // only if it is in the list of the thrown exceptions
     preserveType = false;

     final Class<?> exceptions[] = method.getExceptionTypes();

     for (Class<?> cls : exceptions) {
        if (cls.isInstance(e)) {
           preserveType = true;
           break;
        }
     }
  }

  if (preserveType)
  {
     // it is throws exception - preserve the type
     Constructor<Exception> constructor;
     Exception newEx = null;
     try {
        constructor = ((Constructor<Exception>) e.getClass().getConstructor());
        newEx = constructor.newInstance(message, e);
     } catch (Exception ignore) {
        // ignore this exception we prefer to throw the original
        newEx = null;
     }

     if (newEx != null)
        throw newEx;
  }

  // if we get here this means we do not want, or we cannot preserve the type
  // just rethrow it with the default type

  Constructor<Exception> constructor;
  Exception newEx = null;

  if (defaultException != null) {
     try {
        constructor = (Constructor<Exception>) defaultException.getConstructor();
        newEx = constructor.newInstance(message, e);
     } catch (Exception ignore) {
        // ignore this exception we prefer to throw the original
        newEx = null;
     }

     if (newEx != null)
        throw newEx;
  }

  // if we get here we were unable to construct the default exception
  // there lets log the message that we are going to lose and rethrow
  // the original exception

  log.warn("this message was not propagated as part of the exception: \"" + message + "\"");
  throw e;
}

更新 1: 我可以使用RuntimeException 来避免需要抛出声明,但在这种情况下,我会丢失异常的类型,这是最重要的一点。

有什么办法可以解决这个问题?

【问题讨论】:

  • 不要这样做!!!您将丢失异常堆栈跟踪信息,这是异常中一些最重要的信息。相反,抛出一个新异常(所有情况下的单个“MyFunctionFailedException”类),其中旧异常是“原因”。
  • 如果我将前一个异常作为参数传递给新异常,我认为我不会这样做。
  • 只需抛出“MyFunctionFailedException”。
  • 这完全没有必要。如果你不能处理你所在的异常,就让它冒泡,直到你可以处理它。如果您可以通过抛出完全不同类型的异常来处理它,则抛出其他类型的异常。
  • 重点是保留类型,但要使用更多细节扩展消息。我认为这很有意义。这个网站上有几个问题怎么做。

标签: java exception rethrow


【解决方案1】:

我猜你正在做实际工作的代码(即你没有修补异常的部分)看起来像这样。

public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    try
    {
        // some code that could throw ExceptionA
        ...
        // some code that could throw OtherExceptionA
        ...
        // some code that could throw ExceptionB
        ...
        // some code that could throw OtherExceptionB
    }
    catch (Exception e) 
    {
        if( e instanceof ExceptionA )
        {
            throw new ExceptionA("extra message", e);
        }
        if( e instanceof ExceptionB )
        {
            throw new ExceptionB("extra message", e);
        }

        throw new DefaultException("extra message", e);
     }
}

有两种更好的方法

第一种方法

public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    // some code that could throw ExceptionA
    ...
    try
    {
        // some code that could throw OtherExceptionA
        ...
    }
    catch (Exception e) 
    {
        throw new DefaultException("extra message", e);
    }
    // some code that could throw ExceptionB
    ...
    try
    {
        // some code that could throw OtherExceptionB
    }
    catch (Exception e) 
    {
        throw new DefaultException("extra message", e);
    }
}

第二种方法

public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    try
    {
        // some code that could throw ExceptionA
        ...
        // some code that could throw OtherExceptionA
        ...
        // some code that could throw ExceptionB
        ...
        // some code that could throw OtherExceptionB
    }
    catch (OtherExceptionA | OtherExceptionB e) 
    {
        throw new DefaultException("extra message", e);
    }
}

第一种方法很好,如果您想不惜一切代价继续执行并在遇到RuntimeExceptions 时捕获并包装它们。一般你不想这样做,最好让它们向上传播,因为你可能无法处理它们。

第二种方法通常是最好的。在这里,您明确指出您可以处理哪些异常,并通过包装它们来处理它们。意外的RuntimeExceptions 会向上传播,除非你有办法处理它们,否则它们应该会向上传播。

只是一般性评论:玩StackTraceElements 并不是一个好主意。您最终可能会从 Thread.currentThread().getStackTrace() 得到一个空数组(尽管如果使用现代 Oracle JVM,您很可能不会),并且调用方法的深度并不总是 length-2,它可能是 length-1,尤其是在旧版本的 Oracle JVM。

您可以在this question 中阅读有关此问题的更多信息。

【讨论】:

  • 不发表评论就拒绝投票对任何人都没有帮助。
  • 这不是我投反对票的,但是如果我有 foo() 抛出 ExceptionA、ExceptionB 的话,它的用法会如何?
  • 问题在于建议的泛型声明并没有消除调用者方法在其 throws 声明中添加指定的扩展类型的需要。
【解决方案2】:

为了详细说明)有些人告诉你的是什么,这是 MyFunctionFailedException,当然它应该被命名为更明智的名称:

public class MyFunctionFailedException extends Exception {
    public MyFunctionFailedException(String message, Throwable cause) {
        super(message, cause);
    }
}

那么你的 catch 块就变成了这样。

try {
...
} catch (Exception e) {
    throw new MyFunctionFailedException("extra message", e);
}

如果你真的想重新抛出一个较低级别的异常,你应该使用多个 catch 块。请注意,并非所有类型的异常都必须具有让您添加原因的构造函数。而且您真的应该考虑一下为什么让例如未捕获的 SQLException 在调用堆栈中冒泡对您的方法有意义。

【讨论】:

  • 谢谢,但如果我在我的示例中使用它,我似乎应该知道这一点我要解决什么问题......throw new DefaultException("extra message", e);的行
  • 当然。问题是您要解决的问题是您自己的创作之一,由糟糕的设计(关于放置责任)产生。几乎可以肯定,在您的示例中使用该方法的代码实际上并不关心异常的原因是SQLExceptionSocketTimeoutException 还是IOException,因为它对此无能为力。它会关心DataAccessException/DefaultException/MyFunctionFailedException,因为它可以做一些明智的事情。
  • 我不确定您在第二部分中所说的甚至完全正确的内容是否与您评论第一部分的结论有关。在每种方法中,您都必须决定要处理哪些异常以及传播哪些异常。这不是我自己创造的问题。我要解决的问题是在代码中只有一个位置来表达我的决定——而不是在我想添加额外消息时使用两个位置。
猜你喜欢
  • 1970-01-01
  • 2019-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多