目录
- 前言
- 问题
- 为什么产生
- 如何解决
- 最终方案
- 总结
前言
我们在开发应用系统时,不可避免的要使用到我们自己定义的异常,所以我们一般通常会用到自定义的业务异常类BusinessException,这个异常会继承extends RuntimeException,当发生业务限制的时候,会throw出来。
问题
在SpringMVC项目中,我们可以采用@ControllerAdvice注解,拦截我们的业务异常类,然后做一些处理。
网上有很多SpringMVC项目如何统一处理异常。老顾这里就不做介绍
但是现在项目都是微服务化的,在使用dubbo构建项目时,会发现provider抛出自定义业务异常,在custom端并不能正确的捕获 。即便我们在provider和custom都有导入相同jar包下面的BusinessException异常,并且抛出这个异常。下面是出错情况
老顾采用dubbo版本为2.7.3版本,跟老版本有点区别,但区别不大
provicer生产者代码
其中的DataNotFoundException继承了BusinessException
<code> DataNotFoundException的编码以及错误信息:
PUBLIC_DATA_NOT_FOUND(1001,"数据没有找到")/<code>
consumer消费者代码
运行,输入不存在的goodsID,我们期望我们自定义的异常类被我们拦截到,并做一些处理;但是provider生产端抛给消费者的异常竟然是RuntimeException,只是里面的message是我们的业务异常调用栈信息。如下
不是我们希望的抛给消费者端是DataNotFoundException异常。导致我们消费端没法针对不同的业务进行不同的处理。消费端做了SpringMVC的异常处理返回了
上面的code为1401,是系统异常编码,并不是DataNotFoundException编码
我们定义的DataNotFoundException的编码为:1001
为什么产生
我们来看看dubbo的源码进行分析,如果Dubbo的 provider端 抛出异常(Throwable),则会被 provider端 的ExceptionFilter拦截到,执行以下invoke方法,
里面有个实现Listener类,重写了onResponse。我们来分析一些代码
<code>if (appResponse.hasException() && GenericService.class != invoker.getInterface())/<code>
上面代码的含义就是 如果有异常并且未实现GenericService接口,进入后续判断逻辑,否则直接返回结果。
上面代码的含义就是 不是RuntimeException类型的异常,并且是受检异常(继承Exception),直接抛出。
上面代码的含义就是 在方法签名上有声明,直接抛出
上面代码的含义就是 如果异常类和接口类在同一个jar包中,直接抛出。
上面代码的含义就是 以java.或javax.开头的异常直接抛出。
上面代码的含义就是 dubbo自身的异常,直接抛出。
不满足上述条件,会做toString处理并被封装成RuntimeException抛出
现在我们知道了 为什么我们自定义的异常,没有正确的抛出,这个是因为上面的几个条件,我们都没有满足,
所以最后dubbo把它封装成了RuntimeException。如何解决
解决方法就针对上面的几个条件进行,有几种方案,我们一一看一下
1、将该异常的包名以java.或者javax. 开头
这个方案不现实,也不符合规范,所以不采用
2、业务异常继承Exception,变为checked异常
自定义的业务异常本身属于RuntimeException,所以也不采用
3、异常类和接口类在同一jar包里
较大的项目一般都会有一些common包,定义好异常类型,使用二方包的方式引用,所以也不适用
4、provider的api明确写明throws XxxException
作为生产服务端,不应显式抛出异常给客户的进行处理,所以也不适用
最终方案
以上方案都不合适,我们这里介绍个最终的方案,即采用dubbo的filter重写,dubbo的异常处理。
1、将dubbo源码中ExceptionFilter复制到我们的项目改名为DubboExceptionFilter
修改一些代码
2、在此处加上一段代码来过滤我们项目中的异常 ,以免被dubbo重新封装
3、在resources目录下添加纯文本文件META-INF/dubbo/com.alibaba.dubbo.rpc.Filter并添加内容
<code>dubboExceptionFilter=com.rainbow.goods.server.filter.DubboExceptionFilter/<code>
4、修改dubbo 的配置文件,将DubboExceptionFilter加载进去并且去掉自身的ExceptionFilter
<code><provider>/<code>
上面exception就是dubbo默认的处理异常的filter,前面-号就代表去除
我们修改后,在运行
抛出的是我们自定义的异常了,在看看消费端的异常处理最终显示
确实是我们定义的DataNotFoundException业务异常的编码和信息。
总结
上面的是基于重写dubbo的filter处理了异常,网上有一些别的方案,老顾认为不是太合理,不够灵活,利用修改dubbo源码的方式解决更能彻底解决;希望能够给小伙伴带来帮助。谢谢!!!
---End---
最近老顾上传了微服务网关的分享课程,请大家多多支持
1、基于RocketMq的SpringCloud Stream框架实战入门
2、如何搭建消息中间件应用框架之SpringCloud Stream
3、面试必备:网关异常了怎么办?如何做全局异常处理?
4、Gateway网关系列(二):SpringCloud Gateway入门实战,路由规则
5、Gateway网关系列开篇:SpringCloud的官方网关Gateway介绍
6、API网关在微服务架构中的应用,这一篇就够了
7、学习Lambda表达式看这篇就够了,不会让你失望的哦(续篇)
8、Lambda用在哪里?几种场景?
9、为什么会出现Lambda表达式,你知道吗?
10、不说“分布式事务”理论,直接上大厂阿里的解决方案,绝对实用
11、女程序员问到这个问题,让我思考了半天,Mysql的“三高”架构
12、大厂二面:CAP原则为什么只能满足其中两项?而不能同时满足
13、阿里P7二面:聊聊零拷贝的原理
14、秒杀系统的核心点都在这里,快来取
15、你了解如何利用token方式实现分布式Session吗?
16、Mysql索引结构演变,为什么最终会是那个结构呢?让你一看就懂
17、一场比赛涉及到的知识,用通俗易通的方式介绍并发协调
18、企业实战Redis全方面思考,你思考了吗?
19、面试题:Thread的start和run的区别
20、面试题:什么是CAS?CAS的作用以及缺点
21、如何访问redis中的海量数据?避免事故产生
22、如何解决Redis热点问题?以及如何发现热点?
23、如何设计API接口,实现统一格式返回?
24、你真的知道在生产环境下如何部署tomcat吗?
25、分享一线互联网大厂分布式唯一ID设计 之 snowflake方案
26、分享大厂分布式唯一ID设计方案,快来围观
27、你想了解一线大厂的分布式唯一ID生成方案吗?
28、你知道如何处理大数据量吗?(数据拆分篇)
29、如何永不迁移数据和避免热点? 根据服务器指标分配数据量(揭秘篇)
30、你知道怎么分库分表吗?如何做到永不迁移数据和避免热点吗?
31、你了解大型网站的页面静态化吗?
32、你知道如何更新缓存吗?如何保证缓存和数据库双写一致性?
33、你知道怎么解决DB读写分离,导致数据不一致问题吗?
34、DB读写分离情况下,如何解决缓存和数据库不一致性问题?
35、你真的知道怎么使用缓存吗?
36、如何利用锁,防止缓存击穿?重构思想的重要性
37、海量订单产生的业务高峰期,如何避免消息的重复消费?
38、你知道如何保障生产端100%消息投递成功吗?
39、微服务下的分布式session该如何管理?
40、阿里二面:filter、interceptor、aspect应如何选择?很多人中招
41、互联网架构重要组员CDN,很多高级开发都没有实操过,来看这里
42、阿里二面:CDN缓存控制原理,看看能不能难住你
43、SpringCloud Alibaba之Nacos多环境多项目管理
44、SpringCloud Alibaba系列之Nacos配置中心玩法
45、SpringCloud Alibaba之Nacos注册中心
46、SpringCloud Plus版本之SpringCloud Alibaba
47、SpringCloud Alibaba之Nacos集群、持久化
48、SpringCloud Alibaba之Nacos共享配置、灰度配置
49、SpringCloud Alibaba之Sentinel工作原理
50、SpringCloud Alibaba之Sentinel流控管理
51、SpringCloud Alibaba之Sentinel降级管理
52、SpringCloud Alibaba之Sentinel热点参数限流
53、SpringCloud Alibaba之Sentinel的API实战
閱讀更多 老顧聊技術 的文章