记录Dubbo
对于自定义异常的处理方式.前端
web
层统一捕获处理{"code":xxx,"msg":yyy}
其中code
对应为错误码
,msg
对应为异常信息{"code":-1,"msg":"未知错误"}
,同时将异常堆栈信息输出到日志,便于定位问题先来张系统架构图吧,这张图来源自网络,相信如今大部分中小企业的分布式集群架构都是相似这样的设计:java
简要说明下分层架构:nginx
堡垒机
作统一的代理转发,客户端(pc,移动端等)访问由nginx
统一暴露的入口nginx
反向代理,负载均衡到web
服务器,由tomcat
组成的集群,web
层仅仅是做为接口请求的入口,没有实际的业务逻辑web
层再用rpc
远程调用注册到zookeeper
的dubbo
服务集群,dubbo
服务与数据层交互,处理业务逻辑先后端分离,使用json
格式作数据交互,格式能够统一以下:web
{ "code": 200, //状态码:200成功,其余为失败 "msg": "success", //消息,成功为success,其余为失败缘由 "data": object //具体的数据内容,能够为任意格式 }
映射为javabean
能够统必定义为:数据库
/** * @program: easywits * @description: http请求 返回的最外层对象 * @author: zhangshaolin * @create: 2018-04-27 10:43 **/ @Data @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) public class BaseResult<T> implements Serializable{ private static final long serialVersionUID = -6959952431964699958L; /** * 状态码:200成功,其余为失败 */ public Integer code; /** * 成功为success,其余为失败缘由 */ public String msg; /** * 具体的内容 */ public T data; }
返回结果工具类封装:json
/** * @program: easywits * @description: http返回结果工具类 * @author: zhangshaolin * @create: 2018-07-14 13:38 **/ public class ResultUtil { /** * 访问成功时调用 包含data * @param object * @return */ public static BaseResult success(Object object){ BaseResult result = new BaseResult(); result.setCode(200); result.setMsg("success"); result.setData(object); return result; } /** * 访问成功时调用 不包含data * @return */ public static BaseResult success(){ return success(null); } /** * 返回异常状况 不包含data * @param code * @param msg * @return */ public static BaseResult error(Integer code,String msg){ BaseResult result = new BaseResult(); result.setCode(code); result.setMsg(msg); return result; } /** * 返回异常状况 包含data * @param resultEnum 结果枚举类 统一管理 code msg * @param object * @return */ public static BaseResult error(ResultEnum resultEnum,Object object){ BaseResult result = error(resultEnum); result.setData(object); return result; } /** * 全局基类自定义异常 异常处理 * @param e * @return */ public static BaseResult error(BaseException e){ return error(e.getCode(),e.getMessage()); } /** * 返回异常状况 不包含data * @param resultEnum 结果枚举类 统一管理 code msg * @return */ public static BaseResult error(ResultEnum resultEnum){ return error(resultEnum.getCode(),resultEnum.getMsg()); } }
所以,模拟一次前端调用请求的过程能够以下:后端
web
层接口tomcat
@RestController @RequestMapping(value = "/user") public class UserController { @Autowired UserService mUserService; @Loggable(descp = "用户我的资料", include = "") @GetMapping(value = "/info") public BaseResult userInfo() { return mUserService.userInfo(); } }
服务层接口服务器
@Override public BaseResult userInfo() { UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo(); UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId()); return ResultUtil.success(userInfoVo); }
定义一个自定义异常,用于手动抛出异常信息,注意这里基础RuntimeException
为未受检异常
:网络
简单说明,
RuntimeException
及其子类为未受检异常,其余异常为受检异常,未受检异常是运行时抛出的异常,而受检异常则在编译时则强则报错
public class BaseException extends RuntimeException{ private Integer code; public BaseException() { } public BaseException(ResultEnum resultEnum) { super(resultEnum.getMsg()); this.code = resultEnum.getCode(); } ...省略set get方法 }
为了方便对结果统一管理,定义一个结果枚举类:
public enum ResultEnum { UNKNOWN_ERROR(-1, "o(╥﹏╥)o~~系统出异常啦!,请联系管理员!!!"), SUCCESS(200, "success"); private Integer code; private String msg; ResultEnum(Integer code, String msg) { this.code = code; this.msg = msg; } }
web
层统一捕获异常定义BaseController
抽象类,统一捕获由服务层抛出的异常,全部新增Controller
继承该类便可。
public abstract class BaseController { private final static Logger LOGGER = LoggerFactory.getLogger(BaseController.class); /** * 统一异常处理 * * @param e */ @ExceptionHandler() public Object exceptionHandler(Exception e) { if (e instanceof BaseException) { //全局基类自定义异常,返回{code,msg} BaseException baseException = (BaseException) e; return ResultUtil.error(baseException); } else { LOGGER.error("系统异常: {}", e); return ResultUtil.error(ResultEnum.UNKNOWN_ERROR); } } }
web
层接口UserController
继承BaseController
,统一捕获异常服务层假设抛出自定义系统异常BaseException
,代码以下:
@Override public BaseResult userInfo() { UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo(); UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId()); if (userInfoVo != null) { //这里假设抛个自定义异常,返回结果{code:10228 msg:"用户存在!"} throw new BaseException(ResultEnum.USER_EXIST); } return ResultUtil.success(userInfoVo); }
然而调用结果后,上层捕获到的异常却不是BaseException
,而被认为了未知错误抛出了.带着疑问看看Dubbo
对于异常的处理
Dubbo
对于异常有统一的拦截处理,如下是Dubbo
异常拦截器主要代码:
@Override 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(); // directly throw if it's checked exception // 若是是checked异常,直接抛出 if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // directly throw if the exception appears in the signature // 在方法签名上有声明,直接抛出 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 日志 // for the exception not found in method's signature, print ERROR message in server's log. 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 包里,直接抛出 // directly throw if exception class and interface class are in the same jar file. String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) { return result; } // 是JDK自带的异常,直接抛出 // directly throw if it's JDK exception String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // 是Dubbo自己的异常,直接抛出 // directly throw if it's dubbo exception if (exception instanceof RpcException) { return result; } // 不然,包装成RuntimeException抛给客户端 // otherwise, wrap with RuntimeException and throw back to the client 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; } }
简要说明:
jar
包里,则直接抛出Dubbo
自己的异常(RpcException),则直接抛出RuntimeException
抛给客户端到如今问题很明显了,咱们自定义的BaseException
为未受检异常
,何况不符合Dubbo
异常拦截器中直接抛出的要求,Dubbo
将其包装成了RuntimeException
,因此在上层BaseController
中统一捕获为系统未知错误了.
BaseException
和接口类在同一 jar
包里,可是这种方式要在每一个jar
中放置一个异常类,很差统一维护管理BaseException
,这种方式相对简单一些,比较好统一维护,只是每一个接口都要显式声明一下异常罢了,这里我选择这种方式解决为何必定要抛出自定义异常来中断程序运行,用return ResultUtil.error(ResultEnum resultEnum)
强制返回{code:xxx msg:xxx}
结果,不是同样能够强制中断程序运行?
玩过Spring
的确定都知道,Spring
哟声明式事物的概念,即在接口中添加事物注解,当发生异常时,所有接口执行事物回滚..看下方的伪代码:
@Transactional(rollbackFor = Exception.class) public BaseResult handleData(){ //1. 操做数据库,新增数据表A一条数据,返回新增数据主键id //2. 操做数据库,新增数据库B一条数据,以数据表A主键id为外键关联 //3. 执行成功 返回结果 }
在实际问题场景中去阅读源码是最合适的,带着问题有目的的看指定源码会让人有豁然开朗的感受.
更多原创文章会第一时间推送公众号【张少林同窗】,欢迎关注!