<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Dubbo異常處理源碼探究及其最佳實(shí)踐

          共 5990字,需瀏覽 12分鐘

           ·

          2022-02-14 05:05

          點(diǎn)擊上方“Java進(jìn)階學(xué)習(xí)交流”,進(jìn)行關(guān)注

          后臺(tái)回復(fù)“Java”即可獲贈(zèng)Java學(xué)習(xí)資料

          長(zhǎng)安一片月,萬(wàn)戶搗衣聲。?

          1 背景

          在日常業(yè)務(wù)開發(fā)過程中,我們?yōu)榱俗寴I(yè)務(wù)代碼更健壯,遇到錯(cuò)誤時(shí)返回的提示更友好,一般會(huì)自定義一些業(yè)務(wù)異常。根據(jù)業(yè)務(wù)需要,分為自定義受檢異常和非受檢異常

          知識(shí)點(diǎn)回顧

          Exception類及其子類,但不包括 RuntimeException 的子類,統(tǒng)稱為受檢異常。如果方法執(zhí)行過程中有可能拋出此類異常,必須在方法簽名上聲明

          RuntimeException 類及其子類,統(tǒng)稱為非受檢異常。如果方法執(zhí)行過程中有可能拋出此類異常,可以不必在方法簽名上聲明

          課代表所負(fù)責(zé)的項(xiàng)目使用SpringCloudAlibaba落地了微服務(wù),開發(fā)中組內(nèi)兄弟遇到一個(gè)問題:Dubbo RPC調(diào)用時(shí),provider拋出的一個(gè)業(yè)務(wù)類非受檢異常,consumer接到時(shí)卻是RuntimeException 并且message被和堆棧信息拼接到了一起。

          2 問題復(fù)現(xiàn)

          Dubbo 微服務(wù)中,provider 分為apiserviceconsumer只需要引入 api從注冊(cè)中心調(diào)用service 實(shí)例即可。

          當(dāng)service 中拋出一個(gè)自定義的非受檢異常,且其相應(yīng)api包中沒有這個(gè)異常類時(shí),就會(huì)出現(xiàn)異常被包裝為RuntimeException的情況。

          其實(shí)問題分析到這里,基本就有眉目了:Dubbo是一個(gè)RPC框架,客戶端調(diào)用的都是遠(yuǎn)程方法,參數(shù)和返回值都是經(jīng)過序列化和反序列化為字節(jié)數(shù)組傳輸?shù)摹?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">consumer必須認(rèn)識(shí)這個(gè)異常才能反序列化成功。

          很明顯,我們拋的這個(gè)異常 Dubbo認(rèn)為consumer不認(rèn)識(shí),為了避免反序列化失敗,從而對(duì)異常進(jìn)行了包裝。

          下面結(jié)合源碼闡述Dubbo的異常處理機(jī)制。

          3 源碼分析

          Dubbo 遠(yuǎn)程調(diào)用的異常由ExceptionFilter類處理

          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();

          ????????????????????//?如果是checked異常,直接拋出
          ????????????????????if?(!?(exception?instanceof?RuntimeException)?&&?(exception?instanceof?Exception))?{
          ????????????????????????return?result;
          ????????????????????}
          ????????????????????//?在方法簽名上有聲明,直接拋出
          ????????????????????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日志
          ????????????????????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包里,直接拋出
          ????????????????????String?serviceFile?=?ReflectUtils.getCodeBase(invoker.getInterface());
          ????????????????????String?exceptionFile?=?ReflectUtils.getCodeBase(exception.getClass());
          ????????????????????if?(serviceFile?==?null?||?exceptionFile?==?null?||?serviceFile.equals(exceptionFile)){
          ????????????????????????return?result;
          ????????????????????}
          ????????????????????//?是JDK自帶的異常,直接拋出
          ????????????????????String?className?=?exception.getClass().getName();
          ????????????????????if?(className.startsWith("java.")?||?className.startsWith("javax."))?{
          ????????????????????????return?result;
          ????????????????????}
          ????????????????????//?是Dubbo本身的異常,直接拋出
          ????????????????????if?(exception?instanceof?RpcException)?{
          ????????????????????????return?result;
          ????????????????????}

          ????????????????????//?否則,包裝成RuntimeException拋給客戶端
          ????????????????????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;
          ????????}

          通過源碼可以看到,該類的主要功能是返回接口拋出的異常,Dubbo將其定義為如下幾種情況:

          1. 如果是checked異常,直接拋出
          2. 在方法簽名上有聲明,直接拋出
          3. 不符合1,2 的被認(rèn)為是錯(cuò)誤,會(huì)打印error日志,并嘗試如下處理:
            • 異常類和接口類在同一個(gè)jar包里,直接拋出
            • JDK自帶的異常,直接拋出
            • Dubbo本身的異常,直接拋出
            • 否則,包裝成RuntimeException拋給客戶端

          事實(shí)上Dubbo作為RPC框架已經(jīng)把各種拋異常的情況都考慮全了,最后如果Dubbo認(rèn)為consumer不認(rèn)識(shí)這個(gè)異常還會(huì)包裝成RuntimeException兜底,防止反序列化失敗。

          如果發(fā)生了consumer找不到provider所拋異常的這種情況,不客氣地講,一定是開發(fā)者的問題,把這個(gè)歸罪于Dubbo 那可就太冤枉它了!

          4 最佳實(shí)踐

          Dubbo官網(wǎng)->Dubbo 2.7->用戶文檔->服務(wù)化最佳實(shí)踐 中有如下描述:

          分包

          建議將服務(wù)接口、服務(wù)模型、服務(wù)異常等均放在 API 包中,因?yàn)榉?wù)模型和異常也是 API 的一部分,這樣做也符合分包原則:重用發(fā)布等價(jià)原則(REP),共同重用原則(CRP)。

          所以,符合Dubbo 最佳實(shí)踐的provider-api中應(yīng)該包含服務(wù)接口包,服務(wù)模型包,服務(wù)異常包。所有service中用到的異常,都應(yīng)該在api包中聲明,這樣consumer調(diào)用時(shí)才會(huì)符合Dubbo 要求的:

          異常類和接口類在同一個(gè)jar包里,直接拋出

          從而避免被Dubbo 包裝成RuntimeException拋給客戶端。

          所以,針對(duì)文章開頭遇到的問題,我們只需要把provider-service中拋出自定義的非受檢異常 在provider-api中定義,同時(shí)在相應(yīng)的方法上throw出來就可以了,這樣既可以防止被Dubbo包裝,也不會(huì)因?yàn)榉椒ê灻袥]聲明異常而導(dǎo)致Dubbo報(bào)error錯(cuò)誤。而且,因?yàn)槭欠鞘軝z異常,所以也不強(qiáng)制客戶端對(duì)方法進(jìn)行try catch

          一個(gè)可參考的分包實(shí)踐:

          ??+-?scr
          ??????|
          ??????+-?demo
          ??????????|
          ??????????+-?domain?(業(yè)務(wù)域內(nèi)傳輸數(shù)據(jù)用的?DTO)
          ??????????|
          ??????????+-?service?(API?中?service?接口的實(shí)現(xiàn)類)
          ??????????|
          ??????????+-?exception?(業(yè)務(wù)域中的自定義異常)

          5 彎路

          如果 Google 關(guān)鍵字 [Dubbo 異常處理],你會(huì)發(fā)現(xiàn)幾乎所有文章都是下面這幾個(gè)思路:

          1. 自定義一個(gè)ExceptionFilterDubbo使用,兼容自己的業(yè)務(wù)異常類
          2. 在provider 端寫個(gè)AOP攔截所有異常自己處理
          3. unchecked異常改為checked異常

          當(dāng)然,上面這些方法完全可以解決問題,但這是不是有殺雞用牛刀的意思?

          明明是代碼開發(fā)不規(guī)范,沒有遵循最佳實(shí)踐,卻要強(qiáng)行歸罪于底層框架。Dubbo在努力做得通用,而上面的處理方式卻在讓代碼變得緊耦合。

          總結(jié)問題本質(zhì):Dubbo在認(rèn)為consumer找不到異常類時(shí),為了防止發(fā)生反序列化失敗,對(duì)異常進(jìn)行了一層包裝。針對(duì)這一實(shí)質(zhì),我們用最簡(jiǎn)單、高效,影響最小的辦法解決就可以了。

          課代表相信讀者結(jié)合Dubbo 異常處理的源碼,應(yīng)該會(huì)有自己的判斷。

          6 反思

          遇事不決問Google,多數(shù)情況下我們遇到的問題都會(huì)搜到答案,對(duì)于同樣一個(gè)問題,解決的方法可能多種多樣,我們需要做的是找到問題的本質(zhì),舉一反三,根據(jù)自己業(yè)務(wù)的實(shí)際情況選擇最合適的解決方案。

          切勿盲從,須知:盡信書不如無(wú)書。

          -------------------?End?-------------------

          往期精彩文章推薦:

          歡迎大家點(diǎn)贊,留言,轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持

          想加入Python學(xué)習(xí)群請(qǐng)?jiān)诤笈_(tái)回復(fù)【入群

          萬(wàn)水千山總是情,點(diǎn)個(gè)【在看】行不行

          瀏覽 23
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  观看操逼视频 | 伊人网伊人网 | 中文在线A∨在线 | 三级黄色天天天天 | 中日韩在线视频 |