起因
发现这个问题的起因是前端联调接口的时候发现统一的异常处理没有发挥作用,我们定义的处理的异常类型为AppException(国际惯例继承于RuntimeException),但是Dubbo服务端实际返回的异常变成了RuntimeException,我们自定义的异常处理没有发生作用.
深入研究Dubbo的异常处理
针对发现的问题,查阅了相关的代码和源码发现Dubbo有一个自带的过滤器接口声明了,并且实现了一个异常过滤器,当服务端抛出异常给消费端时异常都会被该异常过滤器处理(其实可以转换思维理解一下,rpc无非就是通过特定的网络协议进行数据传输,既然是网络传输,和我们的接口调用就应该类似,就应该存在一个过滤器之类的东西)
接口类名: com.alibaba.dubbo.rpc.Filter ,
实现Filter接口的异常过滤器ExceptionFilter:
在代码68行,所有非继承与RuntimeException的异常都会直接返回
68 if( !(ecxception instanceof RuntimeException )&& (exception instanceof Exception)){
69 return result ;
70 }
而继承与RuntimeException的异常则会在106行经过RuntimeException的封装返回异常到消费端.
106 return new RpcResult(new RuntimeException(StringUtils.toString(exception)))
基于这个原因,我们返回到消费端的异常就不是我们期望得到的异常了,这样就有可能导致异常判断或者异常的统一处理出现问题.
解决方案
方案一:
既然问题已经找到了,剩下的就是码农最喜欢的工作了,复制粘贴,复制一份ExceptionFilter的代码,生成一个新的ExceptionFilter,然后修改一下判断即可.
但是呢,虽然我们知道了出现问题的地方,但是毕竟框架是人家的,我们还是基于少变动的原则,别修改原有的判断而是新增一个判断,只要是我们系统内定的异常就直接返回结果,而不是二次封装:
if ( exception instanceof AppException){// 仅供参考,具体请根据项目的异常进行判断
return result;
}
做了这一步你就可以试试在服务端抛出异常看看消费端接收到的异常信息是不是有变动了,结果发现自定义的ExceptionFilter根本没生效!!!因为我们少做了一个就是把这个异常过滤器被Dubbo认可支持:
在项目的resources目录下 增加一个新目录 META-INF.dubbo ,并在 这个META-INF.dubbo的目录下增加一个名称为com.alibaba.dubbo.rpc 的 .Filter 文件, 全称 : com.alibaba.dubbo.rpc.Filter . 在文件中输入内容: dubboExceptionFilter=com.xxx.xxx.xxx.xxx.DubboExceptionFilter , key是我们给过滤器定义的名称, value是过滤器的具体类, 名称可以随便起,但是建议起的有意义或者直观(给过滤器起名是有用的,只是我这里没有用到,具体的使用可以搜索一下其他大神的用法.)
至此我们之前的异常无法正常抛出的问题就解决了.
但是这个时候你会发现,当发生AppException的时候,并没有日志信息打印出来,其实这是自己写出来的BUG,因为在原生的过滤器上面是有异常信息打印的(没看完文章就去尝试的朋友肯定会觉得我在胡扯,因为你自己测试的时候确实有异常信息打印出来,明明是少写入日志打印的代码,但是为什么有错误日志呢,请思考一下)
但是因为你自己做了判断少打印了异常信息,才有这个问题,加上日志打印就好了,直接复制粘贴:
if (exception instanceof AppException) {
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);
return result;
}
到了这一步你就以为没问题,右上角就把页面关闭了,点赞也没有一个就来了一句还行,但是但是但是,先别走!!!
虽然结果是我们想要的结果,但是仔细看异常信息,你会发现,经过了Dubbo自带的ExceptionFilter之外还有自己刚刚自定义的ExceptionFilter,抛出的异常一共经历了两个ExceptionFilter,作为一个有原则的程序员这是我们不能接受的,既然我们对原生的ExceptionFilter只是做了特定的Exception修改,没什么大的逻辑改动,我们直接去掉Dubbo的ExceptionFilter即可.
方法一:
如果是自动注入则通过修改Dubbo的provider 配置 增加 filter: -exception即可
方法二:
如果是使用Spring的Bean注入 providerConfig的方法,只要在生成ProviderConfig的时候设置 "-exception"即可
XML注入的方法就不写了,具体的百度一下就知道了.
方案二
上面的方法更多的适用在对异常抛出有严格自定义的服务,如果只是单纯的一两个自定义异常,
除了修改异常过滤器,还可以直接在服务端提供的接口上显式抛出自定义的异常类或者被过滤掉的异常类,这样在过滤器中就会直接返回异常,而不是被二次封装到RuntimeException中.
例子:
List listOrder() throws AppException ;