摘要:前言記錄對于自定義異常的處理方式實(shí)現(xiàn)目標(biāo)服務(wù)層異常,直接向上層拋出,層統(tǒng)一捕獲處理如果是系統(tǒng)自定義異常,則返回其中對應(yīng)為錯誤碼,對應(yīng)為異常信息如果非系統(tǒng)自定義異常,返回未知錯誤,同時將異常堆棧信息輸出到日志便于定位問題項目架構(gòu)先來張系統(tǒng)架
前言
記錄Dubbo對于自定義異常的處理方式.
實(shí)現(xiàn)目標(biāo)服務(wù)層異常,直接向上層拋出,web層統(tǒng)一捕獲處理
如果是系統(tǒng)自定義異常,則返回{"code":xxx,"msg":yyy} 其中code對應(yīng)為錯誤碼,msg對應(yīng)為異常信息
如果非系統(tǒng)自定義異常,返回{"code":-1,"msg":"未知錯誤"},同時將異常堆棧信息輸出到日志,便于定位問題
項目架構(gòu)先來張系統(tǒng)架構(gòu)圖吧,這張圖來源自網(wǎng)絡(luò),相信現(xiàn)在大部分中小企業(yè)的分布式集群架構(gòu)都是類似這樣的設(shè)計:
簡要說明下分層架構(gòu):
通常情況下會有專門一臺堡壘機(jī)做統(tǒng)一的代理轉(zhuǎn)發(fā),客戶端(pc,移動端等)訪問由nginx統(tǒng)一暴露的入口
nginx反向代理,負(fù)載均衡到web服務(wù)器,由tomcat組成的集群,web層僅僅是作為接口請求的入口,沒有實(shí)際的業(yè)務(wù)邏輯
web層再用rpc遠(yuǎn)程調(diào)用注冊到zookeeper的dubbo服務(wù)集群,dubbo服務(wù)與數(shù)據(jù)層交互,處理業(yè)務(wù)邏輯
前后端分離,使用json格式做數(shù)據(jù)交互,格式可以統(tǒng)一如下:
{ "code": 200, //狀態(tài)碼:200成功,其他為失敗 "msg": "success", //消息,成功為success,其他為失敗原因 "data": object //具體的數(shù)據(jù)內(nèi)容,可以為任意格式 }
映射為javabean可以統(tǒng)一定義為:
/** * @program: easywits * @description: http請求 返回的最外層對象 * @author: zhangshaolin * @create: 2018-04-27 10:43 **/ @Data @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) public class BaseResultimplements Serializable{ private static final long serialVersionUID = -6959952431964699958L; /** * 狀態(tài)碼:200成功,其他為失敗 */ public Integer code; /** * 成功為success,其他為失敗原因 */ public String msg; /** * 具體的內(nèi)容 */ public T data; }
返回結(jié)果工具類封裝:
/** * @program: easywits * @description: http返回結(jié)果工具類 * @author: zhangshaolin * @create: 2018-07-14 13:38 **/ public class ResultUtil { /** * 訪問成功時調(diào)用 包含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; } /** * 訪問成功時調(diào)用 不包含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 結(jié)果枚舉類 統(tǒng)一管理 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 結(jié)果枚舉類 統(tǒng)一管理 code msg * @return */ public static BaseResult error(ResultEnum resultEnum){ return error(resultEnum.getCode(),resultEnum.getMsg()); } }
因此,模擬一次前端調(diào)用請求的過程可以如下:
web層接口
@RestController @RequestMapping(value = "/user") public class UserController { @Autowired UserService mUserService; @Loggable(descp = "用戶個人資料", include = "") @GetMapping(value = "/info") public BaseResult userInfo() { return mUserService.userInfo(); } }
服務(wù)層接口
@Override public BaseResult userInfo() { UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo(); UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId()); return ResultUtil.success(userInfoVo); }自定義系統(tǒng)異常
定義一個自定義異常,用于手動拋出異常信息,注意這里基礎(chǔ)RuntimeException為未受檢異常:
簡單說明,RuntimeException及其子類為未受檢異常,其他異常為受檢異常,未受檢異常是運(yùn)行時拋出的異常,而受檢異常則在編譯時則強(qiáng)則報錯
public class BaseException extends RuntimeException{ private Integer code; public BaseException() { } public BaseException(ResultEnum resultEnum) { super(resultEnum.getMsg()); this.code = resultEnum.getCode(); } ...省略set get方法 }
為了方便對結(jié)果統(tǒng)一管理,定義一個結(jié)果枚舉類:
public enum ResultEnum { UNKNOWN_ERROR(-1, "o(╥﹏╥)o~~系統(tǒng)出異常啦!,請聯(lián)系管理員!!!"), SUCCESS(200, "success"); private Integer code; private String msg; ResultEnum(Integer code, String msg) { this.code = code; this.msg = msg; } }web層統(tǒng)一捕獲異常
定義BaseController抽象類,統(tǒng)一捕獲由服務(wù)層拋出的異常,所有新增Controller繼承該類即可。
public abstract class BaseController { private final static Logger LOGGER = LoggerFactory.getLogger(BaseController.class); /** * 統(tǒng)一異常處理 * * @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("系統(tǒng)異常: {}", e); return ResultUtil.error(ResultEnum.UNKNOWN_ERROR); } } }驗證
以上web層接口UserController繼承BaseController,統(tǒng)一捕獲異常
服務(wù)層假設(shè)拋出自定義系統(tǒng)異常BaseException,代碼如下:
@Override public BaseResult userInfo() { UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo(); UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId()); if (userInfoVo != null) { //這里假設(shè)拋個自定義異常,返回結(jié)果{code:10228 msg:"用戶存在!"} throw new BaseException(ResultEnum.USER_EXIST); } return ResultUtil.success(userInfoVo); }
然而調(diào)用結(jié)果后,上層捕獲到的異常卻不是BaseException,而被認(rèn)為了未知錯誤拋出了.帶著疑問看看Dubbo對于異常的處理
Dubbo異常處理Dubbo對于異常有統(tǒng)一的攔截處理,以下是Dubbo異常攔截器主要代碼:
@Override public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException { try { // 服務(wù)調(diào)用 Result result = invoker.invoke(invocation); // 有異常,并且非泛化調(diào)用 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; } // 未在方法簽名上定義的異常,在服務(wù)器端打印 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; } }
簡要說明:
有異常,并且非泛化調(diào)用時,如果是受檢異常,則直接拋出
有異常,并且非泛化調(diào)用時,在方法簽名上有聲明,則直接拋出
有異常,并且非泛化調(diào)用時,異常類和接口類在同一 jar 包里,則直接拋出
有異常,并且非泛化調(diào)用時,是Dubbo本身的異常(RpcException),則直接拋出
有異常,并且非泛化調(diào)用時,剩下的情況,全部都會包裝成RuntimeException拋給客戶端
到現(xiàn)在問題很明顯了,我們自定義的BaseException為未受檢異常,況且不符合Dubbo異常攔截器中直接拋出的要求,Dubbo將其包裝成了RuntimeException,所以在上層BaseController中統(tǒng)一捕獲為系統(tǒng)未知錯誤了.
解決辦法異常類BaseException和接口類在同一 jar 包里,但是這種方式要在每個jar中放置一個異常類,不好統(tǒng)一維護(hù)管理
在接口方法簽名上顯式聲明拋出BaseException,這種方式相對簡單一些,比較好統(tǒng)一維護(hù),只是每個接口都要顯式聲明一下異常罷了,這里我選擇這種方式解決
問題為什么一定要拋出自定義異常來中斷程序運(yùn)行,用return ResultUtil.error(ResultEnum resultEnum) 強(qiáng)制返回{code:xxx msg:xxx}結(jié)果,不是一樣可以強(qiáng)制中斷程序運(yùn)行?
玩過Spring的肯定都知道,Spring喲聲明式事物的概念,即在接口中添加事物注解,當(dāng)發(fā)生異常時,全部接口執(zhí)行事物回滾..看下方的偽代碼:
@Transactional(rollbackFor = Exception.class) public BaseResult handleData(){ //1. 操作數(shù)據(jù)庫,新增數(shù)據(jù)表A一條數(shù)據(jù),返回新增數(shù)據(jù)主鍵id //2. 操作數(shù)據(jù)庫,新增數(shù)據(jù)庫B一條數(shù)據(jù),以數(shù)據(jù)表A主鍵id為外鍵關(guān)聯(lián) //3. 執(zhí)行成功 返回結(jié)果 }
該接口聲明了異常事物回滾,發(fā)送異常時會全部回滾
步驟1數(shù)據(jù)入庫失敗,理論上是拿不到主鍵id的,此時應(yīng)當(dāng)拋出自定義異常,提示操作失敗
如果步驟1數(shù)據(jù)入庫成功,步驟2中數(shù)據(jù)入庫失敗,那么理論上步驟1中的數(shù)據(jù)應(yīng)當(dāng)也要回滾,如果此時強(qiáng)制返回異常結(jié)果,那么步驟1入庫數(shù)據(jù)則成為臟數(shù)據(jù),此時拋出自定義異常是最合理的
最后的思考在實(shí)際問題場景中去閱讀源碼是最合適的,帶著問題有目的的看指定源碼會讓人有豁然開朗的感覺.
更多原創(chuàng)文章會第一時間推送公眾號【張少林同學(xué)】,歡迎關(guān)注!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72830.html
摘要:前言記錄前后端分離的系統(tǒng)應(yīng)用下應(yīng)用場景用戶信息傳遞需求緣起照例先看看系統(tǒng)的一張經(jīng)典架構(gòu)圖,這張圖參考自網(wǎng)絡(luò)在自定義異常,你是怎么處理的中已經(jīng)對該架構(gòu)做了簡單說明,這里不再描述。 showImg(https://segmentfault.com/img/remote/1460000017839927?w=1024&h=768); 前言 記錄前后端分離的系統(tǒng)應(yīng)用下應(yīng)用場景————用戶信息傳...
摘要:全局變量局部變量全局函數(shù)一段也是一塊域。此時打印的自然是,要記住相當(dāng)于,所以這時候改變的是局部變量,并沒有影響到全局變量,所以第二次打印的依然是。 在熟悉了瀏覽器的工作原理之后,今天我們來講講瀏覽器在從服務(wù)器獲取到網(wǎng)頁文件之后是如何解析的。了解了這個基礎(chǔ)知識,對敲出來的代碼,質(zhì)量會有不小的提升。 一、瀏覽器如何解析html html文件在沒有寫入html標(biāo)簽之前和txt文本是一個性質(zhì)的...
摘要:全局變量局部變量全局函數(shù)一段也是一塊域。此時打印的自然是,要記住相當(dāng)于,所以這時候改變的是局部變量,并沒有影響到全局變量,所以第二次打印的依然是。 在熟悉了瀏覽器的工作原理之后,今天我們來講講瀏覽器在從服務(wù)器獲取到網(wǎng)頁文件之后是如何解析的。了解了這個基礎(chǔ)知識,對敲出來的代碼,質(zhì)量會有不小的提升。 一、瀏覽器如何解析html html文件在沒有寫入html標(biāo)簽之前和txt文本是一個性質(zhì)的...
摘要:全局變量局部變量全局函數(shù)一段也是一塊域。此時打印的自然是,要記住相當(dāng)于,所以這時候改變的是局部變量,并沒有影響到全局變量,所以第二次打印的依然是。 在熟悉了瀏覽器的工作原理之后,今天我們來講講瀏覽器在從服務(wù)器獲取到網(wǎng)頁文件之后是如何解析的。了解了這個基礎(chǔ)知識,對敲出來的代碼,質(zhì)量會有不小的提升。 一、瀏覽器如何解析html html文件在沒有寫入html標(biāo)簽之前和txt文本是一個性質(zhì)的...
摘要:此原因在一定程度上阻礙了深度學(xué)習(xí)的發(fā)展,并將大多數(shù)機(jī)器學(xué)習(xí)和信號處理研究,從神經(jīng)網(wǎng)絡(luò)轉(zhuǎn)移到相對較容易訓(xùn)練的淺層學(xué)習(xí)結(jié)構(gòu)。深度學(xué)習(xí)算法可以看成核機(jī)器學(xué)習(xí)中一個優(yōu)越的特征表示方法。 摘要:深度學(xué)習(xí)是一類新興的多層神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)算法。因其緩解了傳統(tǒng)訓(xùn)練算法的局部最小性, 引起機(jī)器學(xué)習(xí)領(lǐng)域的廣泛關(guān)注。首先論述了深度學(xué)習(xí)興起淵源, 分析了算法的優(yōu)越性, 并介紹了主流學(xué)習(xí)算法及應(yīng)用現(xiàn)狀,最后總結(jié)當(dāng)前存在的...
閱讀 2136·2021-09-27 14:04
閱讀 1880·2019-08-30 15:55
閱讀 1705·2019-08-30 13:13
閱讀 1072·2019-08-30 13:07
閱讀 2749·2019-08-29 15:20
閱讀 3246·2019-08-29 12:42
閱讀 3342·2019-08-28 17:58
閱讀 3599·2019-08-28 17:56