【问题标题】:Handling stack traces with unknown exception classes处理具有未知异常类的堆栈跟踪
【发布时间】:2013-09-25 12:57:49
【问题描述】:

我正在实现一个抛出ApplicationExceptions 的会话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

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

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

【问题讨论】:

  • 设计错误。抛出客户不知道的异常是完全没有意义的。
  • Mhh,但在一个巨大的企业应用程序中,我无法了解所有客户端,问题不仅在于抛出的异常。问题是堆栈跟踪中也可能存在未知异常。

标签: java jakarta-ee exception-handling rmi session-bean


【解决方案1】:

由于 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;
}

【讨论】:

  • 应该不是问题,因为在本地我总是想要堆栈跟踪。
  • 你总能得到正确的堆栈跟踪,即使是远程的。这只是为了获得正确的异常type,如果通过Serialization传输该异常类型,则该异常类型在接收方可用。
  • 我认为为所有未知异常创建一个假异常类(如 CarryException)并没有那么有用。
  • @moghaddam:您认为为所有未知异常获取UndeclaredThrowableException 更好?顺便提一句。对于这里的特定问题,甚至不需要“假”例外。但在使用new MyException(new CarryException(legacyException)); 时可能会很有用。客户端仍然会看到它知道的异常。
【解决方案2】:

我想过小迂回的解决方案,但这只是未经检验的猜测。

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

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

【讨论】:

  • 你可以做externalException.setStackTrace(internalException.getStackTrace())。但如果客户端甚至不知道外部异常类型,这也无济于事。
  • 你是对的,但我希望客户知道外部异常,问题出在 UndeclaredThrowableException 中。这是因为我们在这里用内部异常初始化了外部异常 - throw new MyException(e);// e 是 LegacyException
  • 确实,以这种方式设置堆栈跟踪可能会完全解决该特定问题。
【解决方案3】:

这取决于您的客户类型。如果客户是另一个正在开发另一个组件或子系统的团队,我同意你的观点:

拥有堆栈跟踪永远不会是坏事

但是,如果他们是不了解您的应用程序内部结构的客户,那么他们就没有理由知道您的异常类甚至查看您的堆栈跟踪。如果有一个协议可以强制您捕获所有异常并将它们包装在具有error_code 属性的高级异常类中,那就太好了。这样,您可以为应用程序中的每个 catch 语句设置一个特定的错误代码,并为您的客户提供这些代码的列表。

无论如何,从技术角度来看,如果您的客户无法访问您的内部 Exception 类,那么如果没有引用 ClassNotFoundException,他们就无法访问您的堆栈跟踪。如果您真的希望他们看到堆栈跟踪,一种解决方案可能是拥有一个 Aspect,它位于 API 的最上层(将由客户端调用)并捕获所有异常,将它们的堆栈跟踪写入String 并将其作为最终异常的属性发送,该异常将被调用者捕获。这样,调用者可以将堆栈跟踪作为异常的格式化字符串属性访问。

编辑:

您甚至可以配置您的构建脚本,以便此方面永远不会成为您发布版本的一部分。因此,您可以在调试版本中提供此堆栈跟踪消息。

【讨论】:

    猜你喜欢
    • 2017-05-08
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 2012-09-21
    • 1970-01-01
    • 2015-06-23
    • 2011-01-05
    • 2013-07-31
    相关资源
    最近更新 更多