<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>

          求你了,不要再在對外接口中使用枚舉類型了!

          共 3790字,需瀏覽 8分鐘

           ·

          2022-06-12 01:46


          最近,我們的線上環(huán)境出現(xiàn)了一個問題,線上代碼在執(zhí)行過程中拋出了一個IllegalArgumentException,分析堆棧后,發(fā)現(xiàn)最根本的的異常是以下內(nèi)容:


          java.lang.IllegalArgumentException:?

          No?enum?constant?com.a.b.f.m.a.c.AType.P_M

          大概就是以上的內(nèi)容,看起來還是很簡單的,提示的錯誤信息就是在AType這個枚舉類中沒有找到P_M這個枚舉項。


          于是經(jīng)過排查,我們發(fā)現(xiàn),在線上開始有這個異常之前,該應(yīng)用依賴的一個下游系統(tǒng)有發(fā)布,而發(fā)布過程中是一個API包發(fā)生了變化,主要變化內(nèi)容是在一個RPC接口的Response返回值類中的一個枚舉參數(shù)AType中增加了P_M這個枚舉項。
          但是下游系統(tǒng)發(fā)布時,并未通知到我們負責的這個系統(tǒng)進行升級,所以就報錯了。
          我們來分析下為什么會發(fā)生這樣的情況。



          問題重現(xiàn)
          首先,下游系統(tǒng)A提供了一個二方庫的某一個接口的返回值中有一個參數(shù)類型是枚舉類型。


          一方庫指的是本項目中的依賴

          二方庫指的是公司內(nèi)部其他項目提供的依賴

          三方庫指的是其他組織、公司等來自第三方的依賴



          public?interface?AFacadeService?{

          ????public?AResponse?doSth(ARequest?aRequest);

          }

          public?Class?AResponse{

          ????private?Boolean?success;

          ????private?AType?aType;

          }

          public?enum?AType{

          ????P_T,

          ????A_B

          }

          然后B系統(tǒng)依賴了這個二方庫,并且會通過RPC遠程調(diào)用的方式調(diào)用AFacadeService的doSth方法。

          public?class?BService?{

          ????@Autowired

          ????AFacadeService?aFacadeService;

          ????public?void?doSth(){

          ????????ARequest?aRequest?=?new?ARequest();

          ????????AResponse?aResponse?=?aFacadeService.doSth(aRequest);

          ????????AType?aType?=?aResponse.getAType();

          ????}

          }


          這時候,如果A和B系統(tǒng)依賴的都是同一個二方庫的話,兩者使用到的枚舉AType會是同一個類,里面的枚舉項也都是一致的,這種情況不會有什么問題。
          但是,如果有一天,這個二方庫做了升級,在AType這個枚舉類中增加了一個新的枚舉項P_M,這時候只有系統(tǒng)A做了升級,但是系統(tǒng)B并沒有做升級。
          那么A系統(tǒng)依賴的的AType就是這樣的:


          public?enum?AType{

          ????P_T,

          ????A_B,

          ????P_M

          }


          而B系統(tǒng)依賴的AType則是這樣的:


          public?enum?AType{

          ????P_T,

          ????A_B

          }

          這種情況下,在B系統(tǒng)通過RPC調(diào)用A系統(tǒng)的時候,如果A系統(tǒng)返回的AResponse中的aType的類型為新增的P_M時候,B系統(tǒng)就會無法解析。一般在這種時候,RPC框架就會發(fā)生反序列化異常。導致程序被中斷。





          原理分析
          這個問題的現(xiàn)象我們分析清楚了,那么再來看下原理是怎樣的,為什么出現(xiàn)這樣的異常呢。
          其實這個原理也不難,這類RPC框架大多數(shù)會采用JSON的格式進行數(shù)據(jù)傳輸,也就是客戶端會將返回值序列化成JSON字符串,而服務(wù)端會再將JSON字符串反序列化成一個Java對象。
          而JSON在反序列化的過程中,對于一個枚舉類型,會嘗試調(diào)用對應(yīng)的枚舉類的valueOf方法來獲取到對應(yīng)的枚舉。
          而我們查看枚舉類的valueOf方法的實現(xiàn)時,就可以發(fā)現(xiàn),如果從枚舉類中找不到對應(yīng)的枚舉項的時候,就會拋出IllegalArgumentException


          public?static?>?T?valueOf(Class?enumType, String?name)?{

          ????T?result?=?enumType.enumConstantDirectory().get(name);

          ????if?(result?!=?null)

          ????????return?result;

          ????if?(name?==?null)

          ????????throw?new?NullPointerException("Name?is?null");

          ????throw?new?IllegalArgumentException(

          ????????"No?enum?constant?"?+?enumType.getCanonicalName()?+?"."?+?name);

          }


          關(guān)于這個問題,其實在《阿里巴巴Java開發(fā)手冊》中也有類似的約定:


          ?
          里面規(guī)定"對于二方庫的參數(shù)可以使用枚舉,但是返回值不允許使用枚舉"。這背后的思考就是本文上面提到的內(nèi)容。



          擴展思考
          為什么參數(shù)中可以有枚舉?
          不知道大家有沒有想過這個問題,其實這個就和二方庫的職責有點關(guān)系了。
          一般情況下,A系統(tǒng)想要提供一個遠程接口給別人調(diào)用的時候,就會定義一個二方庫,告訴其調(diào)用方如何構(gòu)造參數(shù),調(diào)用哪個接口。
          而這個二方庫的調(diào)用方會根據(jù)其中定義的內(nèi)容來進行調(diào)用。而參數(shù)的構(gòu)造過程是由B系統(tǒng)完成的,如果B系統(tǒng)使用到的是一個舊的二方庫,使用到的枚舉自然是已有的一些,新增的就不會被用到,所以這樣也不會出現(xiàn)問題。
          比如前面的例子,B系統(tǒng)在調(diào)用A系統(tǒng)的時候,構(gòu)造參數(shù)的時候使用到AType的時候就只有P_T和A_B兩個選項,雖然A系統(tǒng)已經(jīng)支持P_M了,但是B系統(tǒng)并沒有使用到。
          如果B系統(tǒng)想要使用P_M,那么就需要對該二方庫進行升級。
          但是,返回值就不一樣了,返回值并不受客戶端控制,服務(wù)端返回什么內(nèi)容是根據(jù)他自己依賴的二方庫決定的。
          但是,其實相比較于手冊中的規(guī)定,我更加傾向于,在RPC的接口中入?yún)⒑统鰠⒍疾灰褂妹杜e。
          一般,我們要使用枚舉都是有幾個考慮:
          • 1、枚舉嚴格控制下游系統(tǒng)的傳入內(nèi)容,避免非法字符。
          • 2、方便下游系統(tǒng)知道都可以傳哪些值,不容易出錯。
          不可否認,使用枚舉確實有一些好處,但是我不建議使用主要有以下原因:
          • 1、如果二方庫升級,并且刪除了一個枚舉中的部分枚舉項,那么入?yún)⒅惺褂妹杜e也會出現(xiàn)問題,調(diào)用方將無法識別該枚舉項。
          • 2、有的時候,上下游系統(tǒng)有多個,如C系統(tǒng)通過B系統(tǒng)間接調(diào)用A系統(tǒng),A系統(tǒng)的參數(shù)是由C系統(tǒng)傳過來的,B系統(tǒng)只是做了一個參數(shù)的轉(zhuǎn)換與組裝。這種情況下,一旦A系統(tǒng)的二方庫升級,那么B和C都要同時升級,任何一個不升級都將無法兼容。
          我其實建議大家在接口中使用字符串代替枚舉,相比較于枚舉這種強類型,字符串算是一種弱類型。
          如果使用字符串代替RPC接口中的枚舉,那么就可以避免上面我們提到的兩個問題,上游系統(tǒng)只需要傳遞字符串就行了,而具體的值的合法性,只需要在A系統(tǒng)內(nèi)自己進行校驗就可以了。
          為了方便調(diào)用者使用,可以使用javadoc的@see注解表明這個字符串字段的取值從那個枚舉中獲取。

          public?Class?AResponse{

          ????private?Boolean?success;

          ????/**

          ????*??@see?AType?

          ????*/


          ????private?String?aType;

          }

          對于像阿里這種比較龐大的互聯(lián)網(wǎng)公司,隨便提供出去的一個接口,可能有上百個調(diào)用方,而接口升級也是常態(tài),我們根本做不到每次二方庫升級之后要求所有調(diào)用者跟著一起升級,這是完全不現(xiàn)實的,并且對于有些調(diào)用者來說,他用不到新特性,完全沒必要做升級。
          還有一種看起來比較特殊,但是實際上比較常見的情況,就是有的時候一個接口的聲明在A包中,而一些枚舉常量定義在B包中,比較常見的就是阿里的交易相關(guān)的信息,訂單分很多層次,每次引入一個包的同時都需要引入幾十個包。
          對于調(diào)用者來說,我肯定是不希望我的系統(tǒng)引入太多的依賴的,一方面依賴多了會導致應(yīng)用的編譯過程很慢,并且很容易出現(xiàn)依賴沖突問題。
          所以,在調(diào)用下游接口的時候,如果參數(shù)中字段的類型是枚舉的話,那我沒辦法,必須得依賴他的二方庫。但是如果不是枚舉,只是一個字符串,那我就可以選擇不依賴。
          所以,我們在定義接口的時候,會盡量避免使用枚舉這種強類型。規(guī)范中規(guī)定在返回值中不允許使用,而我自己要求更高,就是即使在接口的入?yún)⒅形乙埠苌偈褂谩?/span>
          最后,我只是不建議在對外提供的接口的出入?yún)⒅惺褂妹杜e,并不是說徹底不要用枚舉,我之前很多文章也提到過,枚舉有很多好處,我在代碼中也經(jīng)常使用。所以,切不可因噎廢食。
          當然,文中的觀點僅代表我個人,具體是是不是適用其他人,其他場景或者其他公司的實踐,需要讀者們自行分辨下,建議大家在使用的時候可以多思考一下。

          程序汪資料鏈接

          程序汪接的7個私活都在這里,經(jīng)驗整理

          Java項目分享 ?最新整理全集,找項目不累啦 07版

          堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實戰(zhàn)進階

          臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

          字節(jié)跳動總結(jié)的設(shè)計模式 PDF 火了,完整版開放下載!


          歡迎添加程序汪個人微信 itwang009? 進粉絲群或圍觀朋友圈

          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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片 |