5

我想创建一个例程来执行一些日志记录,执行一些其他操作,然后引发异常。我希望从许多不同的位置调用此例程。但是,在此例程中创建异常意味着他们将在其堆栈跟踪中包含此例程。我宁愿堆栈跟踪不报告这个实用程序。有没有办法做到这一点,而无需在调用者中创建异常并将其传递给实用程序例程?

public static void die(String message) throws MyException {
  log(message);
  ...
  throw new MyException();
}

对于 Perl/Java 双语的程序员:我如何Java 中钓鱼?

4

9 回答 9

12

您可以设置要抛出的任何异常的堆栈跟踪:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CarpTest {
    public static void main(String[] args) {
        new CarpTest().run();
    }

    public void run() {
        methodThatCarps();
    }

    private void methodThatCarps() {
        carp("Message");
    }

    private void carp(String message) {
        RuntimeException e = new RuntimeException(message);
        e.fillInStackTrace();
        List<StackTraceElement> stack = new ArrayList<StackTraceElement>(Arrays.asList(e.getStackTrace()));
        stack.remove(0);
        e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
        throw e;
    }
}

这将在运行时打印以下堆栈跟踪:

Exception in thread "main" java.lang.RuntimeException: Message
    at CarpTest.methodThatCarps(CarpTest.java:18)
    at CarpTest.run(CarpTest.java:14)
    at CarpTest.main(CarpTest.java:10)

请注意,您希望方法“carp”不会出现在堆栈跟踪中。然而,堆栈跟踪的操作应该非常小心。

于 2009-04-07T22:42:55.680 回答
4

如果您想使用 Exception 来控制流程以及之后会发生什么,一个很好的建议是重写 fillInStackTrace() 方法:

public Throwable fillInStackTrace() {
   return this;
}

结果,您将获得一个没有堆栈跟踪的异常,并且开销减少(填充堆栈跟踪需要时间)。

于 2009-04-07T22:02:48.333 回答
2

无法从堆栈跟踪中删除 throwing 函数。堆栈跟踪的全部目的是记录异常路径,因此允许函数选择退出会破坏目的。

你可以改变这一点的唯一方法是如果你返回异常而不是抛出它。但这迫使您依赖调用者知道抛出异常。

throw die("someReason).fillInStackTrace();

修改功能

public static Exception die(String message) {  
  log(message);  
  ...  
  return new MyException();
}

编辑

添加了 fillInStackTrace() 调用以确保堆栈重置到抛出点。

http://java.sun.com/j2se/1.3/docs/api/java/lang/Throwable.html#Throwable()

于 2009-04-07T21:20:39.263 回答
1

嗯..你可以继承异常并覆盖其中的所有方法,并包装原始异常。在内部,使用包装异常的 getStackTrace() 方法生成新的堆栈跟踪。我没有查看异常的来源,但您甚至可能不必重写那么多方法。

于 2009-04-07T21:49:44.793 回答
1

也许您应该考虑从不同的方向解决问题。与其修改堆栈跟踪,为什么不让您的异常生成器方法(die在您的示例中)返回异常而不是抛出它?那么你的电话是throw die();.

例如:

// revised die() method:
public static MyException die(String message){
  log(message);
  //...
  return new MyException();
}


// calling code:
throw die("a-whoopsie daisy!");

现在,授予,throw die()可能看起来有点不美观,所以你可以重命名die()newException()其他东西。但是满足异常处理方法不在堆栈跟踪中显示的要求—— die()(或newException())在抛出异常之前返回,因此不是要跟踪的堆栈的一部分。

编辑:我的坏。我花了很多时间使用 C#,以至于我忘记了在 Java 中异常堆栈跟踪是在实例化时生成的,而在 C#/.NET 中,异常堆栈跟踪是在抛出时生成的。

所以这个技巧适用于 C#,但不适用于 Java。

于 2009-04-07T22:52:19.713 回答
1

根据 ordnungswidrig 关于设置堆栈跟踪的说法,以及未知(谷歌)关于覆盖 fillInStackTrace() 的说法,我创建了一个 CarpException,它完全符合我的要求。请注意,我发现我必须删除四个堆栈跟踪帧而不是一个,因为我从 Throwable 和 Exception 中获取帧。

public class CarpException extends Exception {
  @Override
  public Throwable fillInStackTrace() {
    super.fillInStackTrace();
    StackTraceElement[] origStackTrace = getStackTrace();
    StackTraceElement[] newStackTrace = new StackTraceElement[origStackTrace.length - 4];
    System.arraycopy(origStackTrace, 4, newStackTrace, 0, origStackTrace.length - 4);
    setStackTrace(newStackTrace);
    return this;
  }
}
于 2009-04-08T14:27:34.770 回答
0

不可以...我不久前尝试过这样做(我试图在 AOP 存在之前捕获堆栈跟踪以记录方法调用)。

堆栈跟踪是在创建异常时填充的,这是本机完成的。对于我正在处理的事情,我最终阅读了堆栈跟踪并查看了第二个元素,但这对你没有帮助......

于 2009-04-07T21:44:38.567 回答
0

您可能会考虑让您的方法接收一个 Logger 作为该方法的参数。这将允许您根据调用类控制日志记录输出。

我建议不要让您的异常排除堆栈跟踪的这一部分。当您离开并且一些新人开始维护您的代码时,他们不会欣赏这种非标准的错误处理。

于 2009-04-07T21:45:40.747 回答
0

你抛出堆栈跟踪只是为了能够分析它吗?在这种情况下,您可以在返回 StackTraceElement[] 的异常上调用 getStackTrace() 方法。在那里你可以过滤你不想要的元素(例如“die”方法)。

于 2009-04-07T21:46:54.300 回答