小伙伴在使用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中)
- 显示声明异常在方法签名