1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Dubbo異常處理源碼探究及其最佳實踐

        共 5990字,需瀏覽 12分鐘

         ·

        2022-02-14 05:05

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

        后臺回復(fù)“Java”即可獲贈Java學(xué)習(xí)資料

        長安一片月,萬戶搗衣聲。?

        1 背景

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

        知識點回顧

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

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

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

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

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

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

        其實問題分析到這里,基本就有眉目了:Dubbo是一個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)識這個異常才能反序列化成功。

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

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

        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)為是錯誤,會打印error日志,并嘗試如下處理:
          • 異常類和接口類在同一個jar包里,直接拋出
          • JDK自帶的異常,直接拋出
          • Dubbo本身的異常,直接拋出
          • 否則,包裝成RuntimeException拋給客戶端

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

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

        4 最佳實踐

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

        分包

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

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

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

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

        所以,針對文章開頭遇到的問題,我們只需要把provider-service中拋出自定義的非受檢異常 在provider-api中定義,同時在相應(yīng)的方法上throw出來就可以了,這樣既可以防止被Dubbo包裝,也不會因為方法簽名中沒聲明異常而導(dǎo)致Dubboerror錯誤。而且,因為是非受檢異常,所以也不強制客戶端對方法進行try catch。

        一個可參考的分包實踐:

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

        5 彎路

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

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

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

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

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

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

        6 反思

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

        切勿盲從,須知:盡信書不如無書。

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

        往期精彩文章推薦:

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

        想加入Python學(xué)習(xí)群請在后臺回復(fù)【入群

        萬水千山總是情,點個【在看】行不行

        瀏覽 23
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            婷婷色九月 | 娇妻玩4p被3个男子伺候 | 菠萝视频一区 | 黄色免费在线观看 | 中文字幕AV电影 | 欧韩性猛交xxxx乱大交3 | 亚洲精品无码午夜福中文字幕 | 在线视频a | 91久久影院 | 深田咏美av在线播放 |