处理未知异常类的堆栈跟踪

8

我正在实现一个会抛出 ApplicationException 的会话 bean。这些异常有可能包含其它在客户端不可用的异常的连锁堆栈,类似于:

@Override
public void doSomethingSpecial(MyObject o) throws MyException {
    try {
        legacySystem.handle(o);
    } catch (LegacyException e) {
        logger.warn(e.getMessage(), e);
        throw new MyException(e);
    }
}

在这种情况下,客户端可能会得到一个它没有类的异常。这可能会导致:

Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
    at sun.proxy.$Proxy0.doSomethingSpecial(Unknown Source)
    at com.myapp.client.Client.main(Client.java:56)
Caused by: java.lang.ClassNotFoundException: MyLegacyException

我不希望客户端知道服务器端可能抛出的所有异常,但是具有堆栈跟踪永远不会有坏处。

你如何处理这些问题?实现一个拦截器来在将异常发送回客户端时解耦堆栈跟踪是否可行?但是拦截器应该只处理通过RemoteInterface进行的调用,因为在内部我对整个堆栈跟踪感兴趣。


设计有误。抛出客户端不知道的异常完全没有意义。 - user207421
但是在一个巨大的企业应用程序中,我无法知道所有客户端,问题不仅仅是抛出异常。问题在于堆栈跟踪中可能会有未知的异常。 - boskop
3个回答

1
这取决于你的客户端类型。如果客户端是另一个正在开发另一个组件或子系统的团队,我同意你的观点:
“拥有堆栈跟踪永远不会有坏处。”
但如果他们是没有关于你的应用程序内部的概念的客户,那么他们没有理由知道你的异常类甚至看到你的堆栈跟踪。最好有一个协议强制你捕获所有异常并将它们包装在一个高级别的异常类中,其中包含一个错误代码属性。这样,您可以为应用程序中的每个catch语句指定特定的错误代码,并为客户提供这些代码的列表。
从技术角度来看,如果您的客户端没有访问您内部的Exception类,则他们无法在未引用ClassNotFoundException的情况下访问堆栈跟踪。如果您真的希望他们看到堆栈跟踪,一种解决方案是拥有一个Aspect,它坐在您的API最上层(将由客户端调用)并捕获所有异常,将它们的堆栈跟踪写入String并将其作为最终异常的属性发送给调用者。这样,调用者可以将堆栈跟踪作为异常的格式化字符串属性访问。 编辑: 您甚至可以配置构建脚本,以便此Aspect永远不会成为您发布版本的一部分。因此,您可以仅在调试版本中提供此堆栈跟踪消息。

0

由于RMI使用Serialization,您可以使用Serialization功能有条件地替换异常。

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;

public class CarryException extends RuntimeException implements Serializable
{
  final String exceptionClass;

  public CarryException(Exception cause)
  {
    super(cause.getMessage());
    exceptionClass=cause.getClass().getName();
    setStackTrace(cause.getStackTrace());
  }

  @Override
  public String getMessage()
  {
    // if we get here, reconstructing the original exception did not work
    return exceptionClass+": "+super.getMessage();
  }

  /** Invoked by Serialization to get the real instance */
  final Object readResolve() throws ObjectStreamException
  {
    try
    {
      Exception ex = Class.forName(exceptionClass).asSubclass(Exception.class)
        .getConstructor(String.class).newInstance(super.getMessage());
      ex.setStackTrace(getStackTrace());
      return ex;
    }
    catch(InstantiationException|IllegalAccessException|ClassNotFoundException
      | IllegalArgumentException|InvocationTargetException|NoSuchMethodException
      | SecurityException ex)
    {
      // can't reconstruct exception on client side
    }
    return this; // use myself as substitute
  }
}

现在你可以通过throw new CarryException(originalException);向客户端抛出任何异常。 CarryException将始终记录原始异常的堆栈跟踪和消息,并在客户端重建原始异常(如果类可用)。否则,客户端将看到CarryException,因此一个异常类型必须在客户端上已知。
异常类型必须具有标准构造函数,以便重建工作,该构造函数需要一个消息String。(其他所有事情都太复杂了)。但是大多数异常类型都有这个构造函数。
还有一个问题:仅通过Serialization替换只有在涉及Serialization时才起作用,因此当在同一JVM内部时不要直接调用实现类上的方法。否则,您无条件地看到CarryException。因此,即使在本地,您也必须使用存根,例如。
((MyRemoteInterface)RemoteObject.toStub(myImplementation)).doSomethingSpecial();

更新

如果客户端已知道MyException,并且只有LegacyException未知,那么以下代码当然可以正常工作:

catch (LegacyException e) {
    logger.warn(e.getMessage(), e);
    MyException me=new MyException(e.toString());
    me.setStackTrace(e.getStackTrace());
    throw me;
}

在本地环境中,我总是需要堆栈跟踪,所以这不应该是一个问题。 - boskop
你总是可以得到正确的堆栈跟踪,即使是远程的情况下。这只涉及到获取正确的异常类型,如果通过Serialization传输并且接收方可用该异常类型,则可以正常工作。 - Holger
我认为为所有未知异常设置一个假异常类(例如CarryException)可能并不是很有用。 - zaerymoghaddam
1
@moghaddam:你认为对于所有未知异常都获得UndeclaredThrowableException更好吗?顺便说一下,这个“假”的异常在这里并不是必要的。但是当使用类似于new MyException(new CarryException(legacyException));时,它可能会有用。客户端仍然可以看到它所知道的异常。 - Holger

0

我考虑了一个绕路的解决方案,但这只是未经测试的猜测。

你可以使用内部异常初始化外部异常。但是,如果我们查看Throwable的javadoc,我们可以看到get和setStackTrace(StackTraceElement[] stackTrace)方法。

StackTraceElement是用字符串初始化的。因此,也许你可以从内部异常获取堆栈跟踪,并将其设置到外部异常(MyException)中。


你可以执行 externalException.setStackTrace(internalException.getStackTrace())。但是,如果客户端甚至不知道外部异常类型,这也无济于事。 - Holger
你说得没错,但我希望客户知道外部异常,并且问题出在UndeclaredThrowableException上。这是由于我们在这里使用内部异常初始化了外部异常 - throw new MyException(e);//其中e是LegacyException引起的。 - James Cube
确实,以这种方式设置堆栈跟踪可能会完全解决那个特定的问题。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接