我正在实现一个抛出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
我不希望客户端知道可以在服务器端抛出的所有可能的异常,但是堆栈跟踪永远不会坏。
你如何处理这些问题?在将异常发送回客户端时,实现将Interceptor
解耦堆栈跟踪是否是可行的解决方案?但是Interceptor
应该只处理RemoteInterface
的调用,因为内部我对整个堆栈跟踪感兴趣。
答案 0 :(得分:1)
这取决于您的客户类型。如果客户是另一个开发另一个组件或子系统的团队,我同意你的意见:
拥有堆栈跟踪永远不会错误
但如果他们是不了解您的应用程序内部的客户,那么他们就没有理由知道您的异常类,甚至看不到您的堆栈跟踪。如果有一个协议会强制您捕获所有异常并将它们包装在具有error_code
属性的高级异常类中,那将会很不错。这样,您可以为应用程序中的每个catch语句设置特定的错误代码,并为您的客户提供这些代码的列表。
无论如何,从技术角度来看,如果您的客户端无法访问您的内部Exception
类,那么他们无法在没有引用ClassNotFoundException
的情况下访问您的堆栈跟踪。如果你真的希望他们看到堆栈跟踪,一个解决方案可能是拥有一个 Aspect ,它位于API的最上层(将由客户端调用)并捕获所有异常,将它们的堆栈跟踪写入String
并将其作为最终异常的属性发送,该异常将被调用者捕获。这样,调用者可以作为异常的格式化String属性访问堆栈跟踪。
修改强>
您甚至可以配置构建脚本,以便此Aspect永远不会成为您的发布版本的一部分。因此,您可以在调试版本中提供此堆栈跟踪消息。
答案 1 :(得分:0)
我想到了一个小迂回的解决方案,但这只是未经考验的猜测。
您使用内部异常初始化外部异常。但是如果我们看一下Throwable的javadoc,我们可以看到方法get和 setStackTrace(StackTraceElement [] stackTrace)
使用字符串初始化StackTraceElement。所以也许你可以从内部异常中获取堆栈跟踪并将其设置为外部异常(MyException)。
答案 2 :(得分: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;
}