最近在项目上遇到一个有关dubbo的问题,项目分为两层:下层是一些基础服务,上层是业务服务调用下层的基础服务。上层服务的有一个全局的异常拦截器来拦截异常。
@ControllerAdvice@Slf4jpublicclassExceptionFilter {@ExceptionHandlerpublic ResponseEntitydefaultErrorHandler(HttpServletRequest req, Exception e)throws Exception {if(einstanceof MyException){//自定义处理
}
log.error(e.getMessage(), e);
Result res =new Result(500,"SYSTEM_EXCEPTIONS","系统异常");return ResponseEntity.ok(res);
}
}
全局异常可以catch程序上层的异常,进行不同的处理,进行更加友好的提示。
下层抛出的异常:
publicvoidtext(){thrownew MyException("aaaa");
}
然后遇到了一个奇怪的问题,在下层中抛出了一个自定义的异常,但是在上层中捕获异常的时候,却是捕获到runtimeException异常,异常中的message存放了我们需要的对象异常。导致就无法对自定义的异常做特殊处理。
java.lang.RuntimeException:MyException: aaa
查阅了一下资料以后是因为dubbo对异常进行了处理,如果Dubbo的暴露抛出异常(Throwable),则会被暴露方的ExceptionFilter拦截到,执行以下invoke方法:
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {try {
Result result = invoker.invoke(invocation);if (result.hasException() && GenericService.class != invoker.getInterface()) {try {
Throwableexception = result.getException();// 如果是checked异常,直接抛出if (! (exceptioninstanceof RuntimeException) && (exceptioninstanceofException)) {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 (exceptioninstanceof RpcException) {return result;
}// 否则,包装成RuntimeException抛给客户端returnnew 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;
}
}
从代码可以看出,duubo只会抛出受检
异常、jdk自身的异常、方法上申明的异常、dubbo自带的RpcException异常等等,其他的都会包装成new RuntimeException(StringUtils.toString(exception))抛出,所以也有了我遇到的问题。
只要的发生问题的原因就可以解决问题:
- 可以在暴露方法的时候声明异常
- 可以把异常设置为受检异常
- 可以把异常和api包放在同一个jar包中
- provider实现GenericService接口
- 自定义异常继承dubbo的异常类
上述方法是在ExceptionFilter的基础上,可以换个思路,通过订制错误码,把遗产放在对象中进行处理,不过这个思路对使用方的改动较大,需要在开始定义架构的时候的时候进行约定。也可以使用修改配置文件跳过ExceptionFilter这个拦截器,但是这样做暴露方会把所有的原始异常暴露给使用者。
跳过异常拦截器的配置
<dubbo:provider .......... filter="-exception"/>