求你了,不要再在對外介面中使用列舉型別了!
阿新 • • 發佈:2020-11-30
[GitHub 18k Star 的Java工程師成神之路,不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer)
[GitHub 18k Star 的Java工程師成神之路,真的不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer)
[GitHub 18k Star 的Java工程師成神之路,真的真的不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer)
最近,我們的線上環境出現了一個問題,線上程式碼在執行過程中丟擲了一個IllegalArgumentException,分析堆疊後,發現最根本的的異常是以下內容:
java.lang.IllegalArgumentException:
No enum constant com.a.b.f.m.a.c.AType.P_M
大概就是以上的內容,看起來還是很簡單的,提示的錯誤資訊就是在AType這個列舉類中沒有找到P_M這個列舉項。
於是經過排查,我們發現,在線上開始有這個異常之前,該應用依賴的一個下游系統有釋出,而釋出過程中是一個API包發生了變化,主要變化內容是在一個RPC介面的Response返回值類中的一個列舉引數AType中增加了P_M這個列舉項。
但是下游系統釋出時,並未通知到我們負責的這個系統進行升級,所以就報錯了。
我們來分析下為什麼會發生這樣的情況。
### 問題重現
首先,下游系統A提供了一個二方庫的某一個介面的返回值中有一個引數型別是列舉型別。
> 一方庫指的是本專案中的依賴 二方庫指的是公司內部其他專案提供的依賴 三方庫指的是其他組織、公司等來自第三方的依賴
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系統依賴了這個二方庫,並且會通過RPC遠端呼叫的方式呼叫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系統依賴的都是同一個二方庫的話,兩者使用到的列舉AType會是同一個類,裡面的列舉項也都是一致的,這種情況不會有什麼問題。
但是,如果有一天,這個二方庫做了升級,在AType這個列舉類中增加了一個新的列舉項P_M,這時候只有系統A做了升級,但是系統B並沒有做升級。
那麼A系統依賴的的AType就是這樣的:
public enum AType{
P_T,
A_B,
P_M
}
而B系統依賴的AType則是這樣的:
public enum AType{
P_T,
A_B
}
這種情況下**,在B系統通過RPC呼叫A系統的時候,如果A系統返回的AResponse中的aType的型別位新增的P_M時候,B系統就會無法解析。一般在這種時候,RPC框架就會發生反序列化異常。導致程式被中斷。**
### 原理分析
這個問題的現象我們分析清楚了,那麼再來看下原理是怎樣的,為什麼出現這樣的異常呢。
其實這個原理也不難,這類**RPC框架大多數會採用JSON的格式進行資料傳輸**,也就是客戶端會將返回值序列化成JSON字串,而服務端會再將JSON字串反序列化成一個Java物件。
而JSON在反序列化的過程中,對於一個列舉型別,會嘗試呼叫對應的列舉類的valueOf方法來獲取到對應的列舉。
而我們檢視列舉類的valueOf方法的實現時,就可以發現,**如果從列舉類中找不到對應的列舉項的時候,就會丟擲IllegalArgumentException**:
public static