自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 開發(fā)工具
我只是不建議在對外提供的接口的出入?yún)⒅惺褂妹杜e,并不是說徹底不要用枚舉,我之前很多文章也提到過,枚舉有很多好處,我在代碼中也經(jīng)常使用。所以,切不可因噎廢食。

[[354980]]

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

  1. java.lang.IllegalArgumentException:  
  2.  
  3. No enum constant com.a.b.f.m.a.c.AType.P_M 

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

于是經(jīng)過排查,我們發(fā)現(xiàn),在線上開始有這個異常之前,該應用依賴的一個下游系統(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)部其他項目提供的依賴

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

  1. public interface AFacadeService { 
  2.  
  3.     public AResponse doSth(ARequest aRequest); 
  4.  
  5.  
  6. public Class AResponse{ 
  7.  
  8.     private Boolean success; 
  9.  
  10.     private AType aType; 
  11.  
  12.  
  13. public enum AType{ 
  14.  
  15.     P_T, 
  16.  
  17.     A_B 
  18.  

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

  1. public class BService { 
  2.  
  3.     @Autowired 
  4.  
  5.     AFacadeService aFacadeService; 
  6.  
  7.     public void doSth(){ 
  8.  
  9.         ARequest aRequest = new ARequest(); 
  10.  
  11.         AResponse aResponse = aFacadeService.doSth(aRequest); 
  12.  
  13.         AType aType = aResponse.getAType(); 
  14.  
  15.     } 
  16.  

這時候,如果A和B系統(tǒng)依賴的都是同一個二方庫的話,兩者使用到的枚舉AType會是同一個類,里面的枚舉項也都是一致的,這種情況不會有什么問題。

但是,如果有一天,這個二方庫做了升級,在AType這個枚舉類中增加了一個新的枚舉項P_M,這時候只有系統(tǒng)A做了升級,但是系統(tǒng)B并沒有做升級。

那么A系統(tǒng)依賴的的AType就是這樣的:

  1. public enum AType{ 
  2.  
  3.     P_T, 
  4.  
  5.     A_B, 
  6.  
  7.     P_M 
  8.  

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

  1. public enum AType{ 
  2.  
  3.     P_T, 
  4.  
  5.     A_B 
  6.  

這種情況下,在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字符串,而服務端會再將JSON字符串反序列化成一個Java對象。

而JSON在反序列化的過程中,對于一個枚舉類型,會嘗試調(diào)用對應的枚舉類的valueOf方法來獲取到對應的枚舉。

而我們查看枚舉類的valueOf方法的實現(xiàn)時,就可以發(fā)現(xiàn),如果從枚舉類中找不到對應的枚舉項的時候,就會拋出IllegalArgumentException:

  1. public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { 
  2.  
  3.     T result = enumType.enumConstantDirectory().get(name); 
  4.  
  5.     if (result != null
  6.  
  7.         return result; 
  8.  
  9.     if (name == null
  10.  
  11.         throw new NullPointerException("Name is null"); 
  12.  
  13.     throw new IllegalArgumentException( 
  14.  
  15.         "No enum constant " + enumType.getCanonicalName() + "." + name); 
  16.  

關(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,那么就需要對該二方庫進行升級。

但是,返回值就不一樣了,返回值并不受客戶端控制,服務端返回什么內(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注解表明這個字符串字段的取值從那個枚舉中獲取。

  1. public Class AResponse{ 
  2.  
  3.     private Boolean success; 
  4.  
  5.     /** 
  6.  
  7.     *  @see AType  
  8.  
  9.     */ 
  10.  
  11.     private String aType; 
  12.  

對于像阿里這種比較龐大的互聯(lián)網(wǎng)公司,隨便提供出去的一個接口,可能有上百個調(diào)用方,而接口升級也是常態(tài),我們根本做不到每次二方庫升級之后要求所有調(diào)用者跟著一起升級,這是完全不現(xiàn)實的,并且對于有些調(diào)用者來說,他用不到新特性,完全沒必要做升級。

還有一種看起來比較特殊,但是實際上比較常見的情況,就是有的時候一個接口的聲明在A包中,而一些枚舉常量定義在B包中,比較常見的就是阿里的交易相關(guān)的信息,訂單分很多層次,每次引入一個包的同時都需要引入幾十個包。

對于調(diào)用者來說,我肯定是不希望我的系統(tǒng)引入太多的依賴的,一方面依賴多了會導致應用的編譯過程很慢,并且很容易出現(xiàn)依賴沖突問題。

所以,在調(diào)用下游接口的時候,如果參數(shù)中字段的類型是枚舉的話,那我沒辦法,必須得依賴他的二方庫。但是如果不是枚舉,只是一個字符串,那我就可以選擇不依賴。

所以,我們在定義接口的時候,會盡量避免使用枚舉這種強類型。規(guī)范中規(guī)定在返回值中不允許使用,而我自己要求更高,就是即使在接口的入?yún)⒅形乙埠苌偈褂谩?/p>

最后,我只是不建議在對外提供的接口的出入?yún)⒅惺褂妹杜e,并不是說徹底不要用枚舉,我之前很多文章也提到過,枚舉有很多好處,我在代碼中也經(jīng)常使用。所以,切不可因噎廢食。

當然,文中的觀點僅代表我個人,具體是是不是適用其他人,其他場景或者其他公司的實踐,需要讀者們自行分辨下,建議大家在使用的時候可以多思考一下。

【本文是51CTO專欄作者Hollis的原創(chuàng)文章,作者微信公眾號Hollis(ID:hollischuang)】 

戳這里,看該作者更多好文

 

責任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2017-07-03 15:04:41

2020-09-22 09:05:45

MySQLUTF-8utf8mb4

2021-09-30 06:13:36

打印日志error

2020-05-09 10:18:31

Java開源工具

2020-12-04 10:05:00

Pythonprint代碼

2020-04-07 08:34:00

===開發(fā)語言

2020-12-02 11:18:50

print調(diào)試代碼Python

2020-06-15 08:12:51

try catch代碼處理器

2021-05-11 07:10:18

標準庫DjangoOS

2023-09-27 10:19:37

類型video函數(shù)

2019-11-20 23:44:29

接口數(shù)據(jù)加密數(shù)據(jù)安全

2024-03-06 08:36:36

2020-12-11 09:24:19

Elasticsear存儲數(shù)據(jù)

2020-10-12 10:45:44

nullava程序員

2024-06-12 13:54:37

編程語言字符串代碼

2020-12-15 08:06:45

waitnotifyCondition

2025-02-10 08:05:03

2020-06-23 14:52:04

Python無用分號語言

2020-04-16 08:22:11

HTTPS加解密協(xié)議

2012-01-10 10:23:20

鐵路網(wǎng)絡接口
點贊
收藏

51CTO技術(shù)棧公眾號