1、背景
2019年9月5日,fastjson修復了當字符串中包含\x轉義字符時可能引發OOM的問題。建議廣大用戶升級fastjson版本至少到1.2.60。
一個bug這麼恐怖,竟然直接OOM,親身體驗下吧。測試代碼如下:
JSON.parse("[{\"a\":\"a\\x]");
實驗效果:4分鐘 堆內存 佔用上升達2G;
這麼牛掰,甲方爸爸高度重視,火速把自己負責的服務的fastjson版本升級到1.2.60,線上運行也相安無事。
如果這就結束了,本文也就不用寫了。⊙﹏⊙‖∣
2、fastjson升級後業務異常
fastjson升級幾天後,一老系統業務發生異常,異常信息如下:
Exception in thread "xxx" com.alibaba.fastjson.JSONException: expect ':' at 0, actual =
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:290)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1380)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1346)
at com.alibaba.fastjson.JSON.parse(JSON.java:156)
at com.alibaba.fastjson.JSON.parse(JSON.java:166)
at com.alibaba.fastjson.JSON.parse(JSON.java:135)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:227)
at alibaba.fastjson.FastJsonBug.main(FastJsonBug.java:70)
看這錯誤,肯定是json字符串格式有誤,應該是冒號的地方實際上是等號了,然後導致反序列化異常,果斷排查接口入參,結果入參一切正常。納尼。。。
好吧,那就本地debug吧,結果竟然在本地復現異常了,震驚!!!再次檢查接口入參,沒有問題,和以前正常運行的入參是一致的。想到最近升級fastjson了,還原fastjson版本試試吧。還原後還真是正常了!!!
難道fastjson版本升級出了大bug?
本着對阿里技術的信任,我決定一探究竟。
3、一探究竟
待反序列化的數據,其格式是2層List嵌套,測試代碼已做脫敏處理(完整源碼見後文github地址):
String json = "{\"bvos\":[{\"names\":[\"zxiaofan\"]}]}";
JSONObject jsonObjectB1 = GSON.fromJson(json, JSONObject.class);
JSONArray jsonArrayB = jsonObjectB1.getJSONArray("bvos");
JSONObject jsonObjectB2 = JSONObject.parseObject(jsonArrayB.get(0).toString());
// 上面這行代碼直接異常了,異常信息如下:
// com.alibaba.fastjson.JSONException: expect ':' at 0, actual =
好奇寶寶們就不要糾結於爲什麼沒有定義好實體再使用TypeReference一步到位啦,千年老代碼確實是這樣的,這也不是本文的重點。
經過debug發現,jsonArrayB.get(0).toString()的值是 {names=[zxiaofan]}。注意了,names後面是等號,不是冒號,這也就能解釋爲什麼異常是“expect ‘:’ at 0, actual =”了。
但爲什麼升級後就異常,沒升級就一切正常呢?繼續研究下,梳理後發現如下值得注意的地方:
- 1、fastjson版本時1.2.54時正常,大於1.2.54後便會異常;
- 2、運行代碼是Google的Gson和阿里的fastjson混用的(json處理全部換成fastjson一切正常);
莫非,是fastjson升級後和Google的Gson不兼容導致?
彷彿看到了曙光。
對比分析了fastjson 1.2.54版本和其之後的版本(以下以1.2.55版本爲例),發現getJSONArray(String key)還真有區別。
// fastjson <version>1.2.54</version>
public JSONArray getJSONArray(String key) {
Object value = this.map.get(key);
if (value instanceof JSONArray) {
return (JSONArray)value;
} else {
return value instanceof String ? (JSONArray)JSON.parse((String)value) : (JSONArray)toJSON(value);
}
}
// fastjson <version>1.2.55</version>
public JSONArray getJSONArray(String key) {
Object value = this.map.get(key);
if (value instanceof JSONArray) {
return (JSONArray)value;
} else if (value instanceof List) {
return new JSONArray((List)value);
} else {
return value instanceof String ? (JSONArray)JSON.parse((String)value) : (JSONArray)toJSON(value);
}
}
經過調試後發現,1.2.54版本在getJSONArray(String key)方法中使用的是(JSONArray)toJSON(value),而1.2.55版本在getJSONArray(String key)方法中使用的是return new JSONArray((List)value)。兩者處理後返回的數據也確實不同。
fastjson 1.2.54 版本:
fastjson 1.2.55 版本:
從調試情況看,1.2.54版本最終返回的是JSONObect,1.2.55版本返回的是LinkedTreeMap。Map結構toString()的結構肯定是“key=value”,而不是json結構。
但是如果將測試代碼中的GSON.fromJson替換成JSON.parseObject,那麼不論fastjson的版本高低,都能正常運行。
至此,我們知道了,fastjson在升級到1.2.55及以上版本後,getJSONArray方法對Google的Gson處理後的數據兼容性降低,或許本文的名字叫做《fastjson與Gson混用引發的bug》更合適。
也不知道這算不算是bug,給官方提了個issue:
fastjson版本升級降低了對Gson的兼容性 #2814。
4、學習下fastjson對各種數據類型的處理
在分析的過程中,看了fastjson中getJSONArray方法對各種數據類型的處理方式,和自己以前寫的類似代碼相比fastjson的代碼更優雅,值得學習。相關方法com.alibaba.fastjson.JSON.toJSON(),有興趣的同學可以看看。
// 此處代碼僅展示核心結構,如需查閱完整代碼請前往github/fastjson查看。
// toJSON簡直是 數據類型分類處理的模板。@zxiaofan
@SuppressWarnings("unchecked")
public static Object toJSON(Object javaObject, SerializeConfig config) {
if (javaObject == null) {
return null;
}
if (javaObject instanceof JSON) {
return javaObject;
}
if (javaObject instanceof Map) {
if (map instanceof LinkedHashMap) {
} else if (map instanceof TreeMap) {
} else {
innerMap = new HashMap(size);
}
return json;
}
if (javaObject instanceof Collection) {
for (Object item : collection) {
}
return array;
}
if (javaObject instanceof JSONSerializable) {
return JSON.parse(json);
}
Class<?> clazz = javaObject.getClass();
if (clazz.isEnum()) {
return ((Enum<?>) javaObject).name();
}
if (clazz.isArray()) {
for (int i = 0; i < len; i) {
}
return array;
}
if (ParserConfig.isPrimitive2(clazz)) {
return javaObject;
}
ObjectSerializer serializer = config.getObjectWriter(clazz);
if (serializer instanceof JavaBeanSerializer) {
return json;
}
String text = JSON.toJSONString(javaObject);
return JSON.parse(text);
}
5、總結
- 正如文中總結,fastjson在升級到1.2.55及以上版本後,getJSONArray方法對Google的Gson處理後的數據兼容性降低,或許本文的名字叫做《fastjson與Gson混用引發的bug》更合適。
- 代碼規範:同一模塊代碼不允許混用Json解析工具;
- 保持敬畏:生產發佈,一定要保持敬畏,對變更充分迴歸;
- 問題很簡單,重要的是思考方式,在尋找答案的過程中學到更多。
敬畏生命,敬畏職責,敬畏規章。
當你認爲沒有錯誤的時候,錯誤一定會來找你。
–《中國機長》
祝君好運!
Life is all about choices!
將來的你一定會感激現在拼命的自己!
【CSDN】【GitHub】【OSCHINA】【掘金】【微信公衆號】