小伙伴在使用dubbo过程中推出了疑问,部分自定义异常不见了,变成了RuntimeException。并且message超长。
如下图
我们来查看一下Dubbo中如何对待自定义异常的。来到ExceptionFilter
/**
* ExceptionInvokerFilter
* <p>
* 功能:
* <ol>
* <li>不期望的异常打ERROR日志(Provider端)<br>
* 不期望的日志即是,没有的接口上声明的Unchecked异常。
* <li>异常不在API包中,则Wrap一层RuntimeException。<br>
* RPC对于第一层异常会直接序列化传输(Cause异常会String化),避免异常在Client出不能反序列化问题。
* </ol>
*
* @author william.liangf
* @author ding.lid
*/
@Activate(group = Constants.PROVIDER)
public class ExceptionFilter implements Filter {
private final Logger logger;
public ExceptionFilter() {
this(LoggerFactory.getLogger(ExceptionFilter.class));
}
public ExceptionFilter(Logger logger) {
this.logger = logger;
}
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
Result result = invoker.invoke(invocation);
if (result.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = result.getException();
// 如果是checked异常,直接抛出
if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}
// 在方法签名上有声明,直接抛出
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return result;
}
}
} catch (NoSuchMethodException e) {
return result;
}
// 未在方法签名上定义的异常,在服务器端打印ERROR日志
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
// 异常类和接口类在同一jar包里,直接抛出
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
return result;
}
// 是JDK自带的异常,直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// 是Dubbo本身的异常,直接抛出
if (exception instanceof RpcException) {
return result;
}
// 否则,包装成RuntimeException抛给客户端
return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
return result;
}
}
return result;
} catch (RuntimeException e) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
throw e;
}
}
}
- 如果是受检异常直接抛出(当然可以,因为必须在签名中声明,如果客户端不引用异常包直接编译报错)
- 如果方法签名上引用,直接抛出(和1一样的原因)
- 判断接口api和抛出的exception是否在一个jar,是直接抛出
- 如果是jdk的异常直接抛出(其实部分jar不遵守该约定,使用了java或者javax的包,可能出错哦,客户端端无法反序列化)
- 如果是Dubbo本身的异常直接抛出(此处代码乍看有问题,instanceof RpcException 不能表示一定是Dubbo自定义的,因为可以继承。可是深入查看该异常为final,不可以继承。)
- 否则将异常转换为RuntimeException
因此出现了如上图的结果。导致在系统中可能出现异常转化为RuntimeException,并且会将堆栈信息一起写入message,导致message超长。(可以通过复写异常类的fillStackTrace来解决过长的问题)
对于自定义异常可能可以做到将异常和接口包装在同一个jar。但是部分依赖外部异常可能无法做到。
解决方案
- 在异常返回到消费者之前提前包装成自定义异常(自定义异常和接口打在同一个jar中)
- 显示声明异常在方法签名
网友评论