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

Java 处理具有未知异常类的堆栈跟踪,java,jakarta-ee,exception-handling,rmi,session-bean,Java,Jakarta Ee,Exception Handling,Rmi,Session Bean,我正在实现一个会话bean,它抛出ApplicationExceptions。 这些异常具有链式堆栈跟踪,其中可能包含其类在客户端上不可用的异常。比如: @Override public void doSomethingSpecial(MyObject o) throws MyException { try { legacySystem.handle(o); } catch (LegacyException e) { logger.warn(e.g

我正在实现一个会话bean,它抛出
ApplicationException
s。 这些异常具有链式堆栈跟踪,其中可能包含其类在客户端上不可用的异常。比如:

@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
我不想让客户机知道所有可能在服务器端抛出的异常,但是拥有堆栈跟踪从来都不是坏事


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

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

拥有堆栈跟踪从来都不是坏事

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

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

编辑:


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

我曾考虑过小迂回解决方案,但这只是未经测试的推测

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


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

由于RMI决定进行
序列化
您可以使用
序列化
功能有条件地替换异常

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
  }
}
现在,您可以通过
抛出new CarryException(originalException)向客户机抛出任何异常
CarryException
将始终记录原始异常的堆栈跟踪和消息,并在客户端重新创建原始异常(如果类可用)。否则,客户端会看到
CarryException
异常,因此客户端必须知道一种异常类型

异常类型必须具有标准构造函数,该构造函数接受消息
String
,才能进行重建。(所有其他事情都太复杂了)。但大多数异常类型都有这种情况

还有一个问题:通过
序列化
替换只在涉及
序列化
的情况下才有效,因此在同一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;
}

您可以执行
externalException.setStackTrace(internalException.getStackTrace())
。但如果客户机甚至不知道外部异常类型,这也没有帮助。你是对的,但我希望客户机知道外部异常,而问题在于未声明的HrowableException。这是因为我们在这里用内部异常初始化了外部异常-抛出新的MyException(e);//e作为LegacyExceptionIndeed,以这种方式设置堆栈跟踪可能会完全解决该特定问题。这不应该是一个问题,因为在本地我始终需要堆栈跟踪。使用此方法,您始终可以获得正确的堆栈跟踪,即使是远程跟踪。这只是关于获得正确的异常类型,如果通过
序列化传输,并且异常类型在接收端可用,则该异常类型有效。我认为为所有未知异常提供一个伪异常类(如CarryException)不会有多大用处。@Moghadam:你认为,为所有未知异常获取
未声明的hrowableexception
更好吗?顺便说一句,对于这里的特定问题,这个“假”例外甚至不是必需的。但是当使用诸如
newmyException(newcarryException(legacyException)),它可能很有用。客户仍然看到它知道的异常。设计是错误的。抛出客户端不知道的异常是完全没有意义的。但是在一个大型企业应用程序中,我不可能知道所有的客户端,问题不仅仅是抛出的异常。问题是堆栈跟踪中也可能存在未知异常。