一個列舉類的set方法引發的血案
事故回顧:
線上叢集環境,其中一臺機器出現了詭異的現象:明明rpc介面響應的結果是成功,但是呼叫方判斷的時候走的是失敗的判斷邏輯,導致持續的報警,並且只有一臺機器有著個現象,先用重啟大法解決了,具體原因是什麼?一堆工程師開始分析。
分析原因:
查日誌,在某個時間點之後開始大量報錯,大家開始圍在一起討論:
這個時間有沒有新上線?沒有。
有沒有可能jar包不正確?不可能,程式碼已經有幾天沒更新了。
甚至想到了黑客攻擊?更不可能。
再回到原來的問題上,響應結果和判斷結果的邏輯都是根據列舉的值,那麼看看列舉值是否正確?程式碼沒問題。明明正確,為什 麼執行時不正確?
執行時不正確的唯一原因就是執行時被修改了,那麼怎麼修改呢,set方法!列舉中的set方法!恍然大悟,趕緊去看看程式碼有沒有這個set方法,果然有,並且還有呼叫的地方,這個呼叫地方的程式碼平常很少被執行到,所以以前一直沒有報出來,今天某個時間點只有這臺機器的程式碼被執行了,所以 這臺機器開始上演詭異的報警。
OK,問題被找到了,修改程式碼,重新上線,下邊分析下這個列舉類。
列舉類:
列舉類是jdk的一個語法糖,其本質是通過普通類實現的,只是編譯器為我們進行了加工處理,每個列舉型別編譯後的位元組碼實質都是繼承自Java.lang.enum的列舉型別同名普通類,而每個列舉常量實質上是一個列舉型別同名普通類的靜態常量物件,所有列舉常量都是通過靜態程式碼塊進行初始化例項賦值。
public enum status{
start("a"),running("b"),stop();
}
我們對如上的列舉型別進行javac編譯後通過Javaap -v Status.class 可以檢視其編譯後的位元組碼如下:
public final class Status extends java.lang.Enum<status> { public static final Status START; public static final Status RUNNING; public static final Status STOP; ..... }
其實寫到這裡,這個問題出現的原因很清楚了,列舉本質是被編譯器處理成類,列舉值是靜態的常量屬性,列舉只是一種語法糖,被編譯器生成最終的類。從某種意義上可以說jdk1.5後引入的列舉型別是列舉常量類的程式碼封裝而已。當用set方法進行賦值的時候,實際上是修改的一個記憶體中的靜態變數的值,這個值原本的意義就被修改了,這時候如果其他地方再來判斷,必然出錯,一場血案就此發生!