Dubbo 自定义异常,你是怎么处理的?
前言
记录Dubbo
对于自定义异常的处理方式.
实现目标
- 服务层异常,直接向上层抛出,
web
层统一捕获处理 - 如果是系统自定义异常,则返回
{"code":xxx,"msg":yyy}
其中code
对应为错误码
,msg
对应为异常信息 - 如果非系统自定义异常,返回
{"code":-1,"msg":"未知错误"}
,同时将异常堆栈信息输出到日志,便于定位问题
项目架构
先来张系统架构图吧,这张图来源自网络,相信现在大部分中小企业的分布式集群架构都是类似这样的设计:
简要说明下分层架构:
- 通常情况下会有专门一台
堡垒机
做统一的代理转发,客户端(pc,移动端等)访问由nginx
统一暴露的入口 nginx
反向代理,负载均衡到web
服务器,由tomcat
组成的集群,web
层仅仅是作为接口请求的入口,没有实际的业务逻辑web
层再用rpc
远程调用注册到zookeeper
的dubbo
服务集群,dubbo
服务与数据层交互,处理业务逻辑
前后端分离,使用json
格式做数据交互,格式可以统一如下:
1 {
2 "code": 200, //状态码:200成功,其他为失败
3 "msg": "success", //消息,成功为success,其他为失败原因
4 "data": object //具体的数据内容,可以为任意格式
5 }
映射为javabean
可以统一定义为:
1/**
2 * @program: easywits
3 * @description: http请求 返回的最外层对象
4 * @author: zhangshaolin
5 * @create: 2018-04-27 10:43
6 **/
7@Data
8@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
9public class BaseResult<T> implements Serializable{
10
11 private static final long serialVersionUID = -6959952431964699958L;
12
13 /**
14 * 状态码:200成功,其他为失败
15 */
16 public Integer code;
17
18 /**
19 * 成功为success,其他为失败原因
20 */
21 public String msg;
22
23 /**
24 * 具体的内容
25 */
26 public T data;
27}
返回结果工具类封装:
1/**
2 * @program: easywits
3 * @description: http返回结果工具类
4 * @author: zhangshaolin
5 * @create: 2018-07-14 13:38
6 **/
7public class ResultUtil {
8
9 /**
10 * 访问成功时调用 包含data
11 * @param object
12 * @return
13 */
14 public static BaseResult success(Object object){
15 BaseResult result = new BaseResult();
16 result.setCode(200);
17 result.setMsg("success");
18 result.setData(object);
19 return result;
20 }
21
22 /**
23 * 访问成功时调用 不包含data
24 * @return
25 */
26 public static BaseResult success(){
27 return success(null);
28 }
29
30 /**
31 * 返回异常情况 不包含data
32 * @param code
33 * @param msg
34 * @return
35 */
36 public static BaseResult error(Integer code,String msg){
37 BaseResult result = new BaseResult();
38 result.setCode(code);
39 result.setMsg(msg);
40 return result;
41 }
42
43 /**
44 * 返回异常情况 包含data
45 * @param resultEnum 结果枚举类 统一管理 code msg
46 * @param object
47 * @return
48 */
49 public static BaseResult error(ResultEnum resultEnum,Object object){
50 BaseResult result = error(resultEnum);
51 result.setData(object);
52 return result;
53 }
54
55 /**
56 * 全局基类自定义异常 异常处理
57 * @param e
58 * @return
59 */
60 public static BaseResult error(BaseException e){
61 return error(e.getCode(),e.getMessage());
62 }
63
64 /**
65 * 返回异常情况 不包含data
66 * @param resultEnum 结果枚举类 统一管理 code msg
67 * @return
68 */
69 public static BaseResult error(ResultEnum resultEnum){
70 return error(resultEnum.getCode(),resultEnum.getMsg());
71 }
72}
因此,模拟一次前端调用请求的过程可以如下:
web
层接口1@RestController
2@RequestMapping(value = "/user")
3public class UserController {
4 @Autowired
5 UserService mUserService;
6 @Loggable(descp = "用户个人资料", include = "")
7 @GetMapping(value = "/info")
8 public BaseResult userInfo() {
9 return mUserService.userInfo();
10 }
11}服务层接口
1 @Override
2public BaseResult userInfo() {
3 UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo();
4 UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId());
5 return ResultUtil.success(userInfoVo);
6}
自定义系统异常
定义一个自定义异常,用于手动抛出异常信息,注意这里基础RuntimeException
为未受检异常
:
简单说明,
RuntimeException
及其子类为未受检异常,其他异常为受检异常,未受检异常是运行时抛出的异常,而受检异常则在编译时则强则报错
1public class BaseException extends RuntimeException{
2
3 private Integer code;
4
5 public BaseException() {
6 }
7
8 public BaseException(ResultEnum resultEnum) {
9 super(resultEnum.getMsg());
10 this.code = resultEnum.getCode();
11 }
12 ...省略set get方法
13}
为了方便对结果统一管理,定义一个结果枚举类:
1public enum ResultEnum {
2 UNKNOWN_ERROR(-1, "o(╥﹏╥)o~~系统出异常啦!,请联系管理员!!!"),
3 SUCCESS(200, "success");
4
5 private Integer code;
6
7 private String msg;
8
9 ResultEnum(Integer code, String msg) {
10 this.code = code;
11 this.msg = msg;
12 }
13}
`web`层统一捕获异常
定义BaseController
抽象类,统一捕获由服务层抛出的异常,所有新增Controller
继承该类即可。
1public abstract class BaseController {
2 private final static Logger LOGGER = LoggerFactory.getLogger(BaseController.class);
3
4 /**
5 * 统一异常处理
6 *
7 * @param e
8 */
9 @ExceptionHandler()
10 public Object exceptionHandler(Exception e) {
11 if (e instanceof BaseException) {
12 //全局基类自定义异常,返回{code,msg}
13 BaseException baseException = (BaseException) e;
14 return ResultUtil.error(baseException);
15 } else {
16 LOGGER.error("系统异常: {}", e);
17 return ResultUtil.error(ResultEnum.UNKNOWN_ERROR);
18 }
19 }
20}
验证
以上
web
层接口UserController
继承BaseController
,统一捕获异常服务层假设抛出自定义系统异常
BaseException
,代码如下:1 @Override
2 public BaseResult userInfo() {
3 UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo();
4 UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId());
5 if (userInfoVo != null) {
6 //这里假设抛个自定义异常,返回结果{code:10228 msg:"用户存在!"}
7 throw new BaseException(ResultEnum.USER_EXIST);
8 }
9 return ResultUtil.success(userInfoVo);
10}
然而调用结果后,上层捕获到的异常却不是BaseException
,而被认为了未知错误抛出了.带着疑问看看Dubbo
对于异常的处理
Dubbo异常处理
Dubbo
对于异常有统一的拦截处理,以下是Dubbo
异常拦截器主要代码:
1 @Override
2 public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
3 try {
4 // 服务调用
5 Result result = invoker.invoke(invocation);
6 // 有异常,并且非泛化调用
7 if (result.hasException() && GenericService.class != invoker.getInterface()) {
8 try {
9 Throwable exception = result.getException();
10
11 // directly throw if it's checked exception
12 // 如果是checked异常,直接抛出
13 if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
14 return result;
15 }
16 // directly throw if the exception appears in the signature
17 // 在方法签名上有声明,直接抛出
18 try {
19 Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
20 Class<?>[] exceptionClassses = method.getExceptionTypes();
21 for (Class<?> exceptionClass : exceptionClassses) {
22 if (exception.getClass().equals(exceptionClass)) {
23 return result;
24 }
25 }
26 } catch (NoSuchMethodException e) {
27 return result;
28 }
29
30 // 未在方法签名上定义的异常,在服务器端打印 ERROR 日志
31 // for the exception not found in method's signature, print ERROR message in server's log.
32 logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
33 + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
34 + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
35
36 // 异常类和接口类在同一 jar 包里,直接抛出
37 // directly throw if exception class and interface class are in the same jar file.
38 String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
39 String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
40 if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
41 return result;
42 }
43 // 是JDK自带的异常,直接抛出
44 // directly throw if it's JDK exception
45 String className = exception.getClass().getName();
46 if (className.startsWith("java.") || className.startsWith("javax.")) {
47 return result;
48 }
49 // 是Dubbo本身的异常,直接抛出
50 // directly throw if it's dubbo exception
51 if (exception instanceof RpcException) {
52 return result;
53 }
54
55 // 否则,包装成RuntimeException抛给客户端
56 // otherwise, wrap with RuntimeException and throw back to the client
57 return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
58 } catch (Throwable e) {
59 logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
60 + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
61 + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
62 return result;
63 }
64 }
65 // 返回
66 return result;
67 } catch (RuntimeException e) {
68 logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
69 + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
70 + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
71 throw e;
72 }
73 }
简要说明:
- 有异常,并且非泛化调用时,如果是受检异常,则直接抛出
- 有异常,并且非泛化调用时,在方法签名上有声明,则直接抛出
- 有异常,并且非泛化调用时,异常类和接口类在同一
jar
包里,则直接抛出 - 有异常,并且非泛化调用时,是
Dubbo
本身的异常(RpcException),则直接抛出 - 有异常,并且非泛化调用时,剩下的情况,全部都会包装成
RuntimeException
抛给客户端
到现在问题很明显了,我们自定义的BaseException
为未受检异常
,况且不符合Dubbo
异常拦截器中直接抛出的要求,Dubbo
将其包装成了RuntimeException
,所以在上层BaseController
中统一捕获为系统未知错误了.
解决办法
- 异常类
BaseException
和接口类在同一jar
包里,但是这种方式要在每个jar
中放置一个异常类,不好统一维护管理 - 在接口方法签名上显式声明抛出
BaseException
,这种方式相对简单一些,比较好统一维护,只是每个接口都要显式声明一下异常罢了,这里我选择这种方式解决
问题
为什么一定要抛出自定义异常来中断程序运行,用return ResultUtil.error(ResultEnum resultEnum)
强制返回{code:xxx msg:xxx}
结果,不是一样可以强制中断程序运行?
玩过Spring
的肯定都知道,Spring
哟声明式事物的概念,即在接口中添加事物注解,当发生异常时,全部接口执行事物回滚..看下方的伪代码:
1@Transactional(rollbackFor = Exception.class)
2public BaseResult handleData(){
3
4 //1. 操作数据库,新增数据表A一条数据,返回新增数据主键id
5
6 //2. 操作数据库,新增数据库B一条数据,以数据表A主键id为外键关联
7
8 //3. 执行成功 返回结果
9}
- 该接口声明了异常事物回滚,发送异常时会全部回滚
- 步骤1数据入库失败,理论上是拿不到主键id的,此时应当抛出自定义异常,提示操作失败
- 如果步骤1数据入库成功,步骤2中数据入库失败,那么理论上步骤1中的数据应当也要回滚,如果此时强制返回异常结果,那么步骤1入库数据则成为脏数据,此时抛出自定义异常是最合理的
最后的思考
在实际问题场景中去阅读源码是最合适的,带着问题有目的的看指定源码会让人有豁然开朗的感觉.
更多原创文章会第一时间推送公众号【张少林同学】,欢迎关注!
Dubbo 自定义异常,你是怎么处理的?的更多相关文章
- dubbo自定义异常传递信息丢失问题解决
访问我的博客 目前计划对已有的单体项目进行组织架构拆分,调研了分布式系统中常用中间件 Dubbo 和 Spring Cloud,选择了 Dubbo,可以对当前现有项目进行平滑升级改造.但是一开始就遇到 ...
- 面试官问我,使用Dubbo有没有遇到一些坑?我笑了。
前言 17年的时候,因为一时冲动没把持住(当然最近也有粉丝叫我再冲动一把再更新一波),结合面试题写了一个系列的Dubbo源码解析.目前公众号大部分粉丝都是之前的粉丝,这里不过多介绍. 根据我的面试经验 ...
- 面试官问我,使用Dubbo有没有遇到一些坑?我笑了
17年的时候,因为一时冲动没把持住(当然最近也有粉丝叫我再冲动一把再更新一波),结合面试题写了一个系列的Dubbo源码解析.目前公众号大部分粉丝都是之前的粉丝,这里不过多介绍. 根据我的面试经验而言, ...
- 13.1 dubbo服务降级源码解析
从 9.1 客户端发起请求源码 的客户端请求总体流程图中,截取部分如下: //代理发出请求 proxy0.sayHello(String paramString) -->InvokerInvoc ...
- Dubbo封装rest服务返回结果
由于Dubbo服务考虑到一个是给其他系统通过RPC调用,另外一个是提供HTTP协议本身系统的后台管理页面,因此Dubbo返回参数在rest返回的时候配置拦截器进行处理. 在拦截器中,对返回参数封装成如 ...
- dubbo异常处理
dubbo异常处理 我们的项目使用了dubbo进行不同系统之间的调用. 每个项目都有一个全局的异常处理,对于业务异常,我们会抛出自定义的业务异常(继承RuntimeException). 全局的异常处 ...
- 用dubbo时遇到的一个序列化的坑
首先,这是标题党,问题并不是出现在序列化上,这是报错的一部分: Caused by: com.alibaba.dubbo.remoting.RemotingException: Failed to s ...
- dubbo服务提供与消费
一.前言 项目中用到了Dubbo,临时抱大腿,学习了dubbo的简单实用方法.现在就来总结一下dubbo如何提供服务,如何消费服务,并做了一个简单的demo作为参考. 二.Dubbo是什么 Dubbo ...
- 分布式学习系列【dubbo入门实践】
分布式学习系列[dubbo入门实践] dubbo架构 组成部分:provider,consumer,registry,monitor: provider,consumer注册,订阅类似于消息队列的注册 ...
随机推荐
- uva10288 Coupons 【概率 分数】
题目: 题意: 一共n种不同的礼券,每次得到每种礼券的概率相同.求期望多少次可以得到所有n种礼券.结果以带分数形式输出.1<= n <=33. 思路: 假设当前已经得到k种,获得新的一种的 ...
- 表示集合的数据结构:数组(Array),对象(Object),Map和Set
Map和Set是ES6标准新增的数据类型 Map: 是一组键值对的结构,使用一个二维数组来初始化Map,例如: var m = new Map([['xiaohong',100],['xiaolan' ...
- 8-cin cout PK scanf printf(速度快慢问题对比)
我们在c++ 中使用cin cout很方便但速度很慢,导致有些题目用cin就超时而用scanf则就ac了,那到底改用谁? cin慢是有原因的,其实默认的时候,cin与stdin总是保持同步的,也就是说 ...
- How to Restart Qt Application
How to restart QtApplication As we know, Restarting Application means to exit current application, t ...
- mysql查询赋值、修改拼接字符串
sql中修改字符串类型的字段可以这么拼接:update tbName set UserName='abc'+UserName; 但mysql中就不行了,需要这样:update tbName set U ...
- [转].NET 4.5+项目迁移.NET Core的问题记录 HTTP Error 502.5
本文转自:http://www.cnblogs.com/ronli/p/5900001.html 这几天试着把目前的开发框架迁移到新的.net core平台,中间遇到的问题在这里简单记录一下. 迁移过 ...
- DataStage 七、在DS中使用配置文件分配资源
DataStage序列文章 DataStage 一.安装 DataStage 二.InfoSphere Information Server进程的启动和停止 DataStage 三.配置ODBC Da ...
- Devexpress VCL Build v2013 vol 13.2.3 发布
继续修修补补,大过年的,就不吐槽了. What's New in 13.2.3 (VCL Product Line) New Major Features in 13.2 What's New i ...
- swift的可选值(optional)
苹果那文档写了一大堆也没有好好的写一下可选值(optional)这个东西.就是在有一个“Optional Chaining”的章节,但是也不是很充分的说明.最后找了半天在“the basics”里墨迹 ...
- Linux 基础教程 36-查看系统性能
uptime uptime命令功能比较简单,主要功能如下所示: 查看服务器的开机时长 查看CPU负载 基本用法 uptime 用法示例 [root@localhost ~]# uptime 1 ...