1. 程式人生 > >廢棄fastjson!大型專案遷移Gson保姆級攻略

廢棄fastjson!大型專案遷移Gson保姆級攻略

# 前言 大家好,又雙叒叕見面了,我是天天放大家鴿子的蠻三刀。 在被大家取關之前,我立下一個“遠大的理想”,一定要在這周更新文章。現在看來,flag有用了。。。 本篇文章是我這一個多月來幫助組內廢棄fastjson框架的總結,我們將大部分Java倉庫從fastjson遷移至了Gson。 這麼做的主要的原因是公司受夠了fastjson頻繁的安全漏洞問題,每一次出現漏洞都要推一次全公司的fastjson強制版本升級,很令公司頭疼。 **文章的前半部分,我會簡單分析各種json解析框架的優劣,並給出企業級專案遷移json框架的幾種解決方案。** **在文章的後半部分,我會結合這一個月的經驗,總結下Gson的使用問題,以及fastjson遷移到Gson踩過的深坑。** **文章目錄:** - 為何要放棄fastjson? - fastjson替代方案 - 三種json框架的特點 - 效能對比 - 最終選擇方案 - 替換依賴時的注意事項 - 謹慎,謹慎,再謹慎 - 做好開發團隊和測試團隊的溝通 - 做好迴歸/介面測試 - 考慮遷移前後的效能差異 - 使用Gson替換fastjson - Json反序列化 - 範型處理 - List/Map寫入 - 駝峰與下劃線轉換 - 遷移常見問題踩坑 - Date序列化方式不同 - SpringBoot異常 - Swagger異常 - @Mapping JsonObject作為入參異常 **注意:是否使用fastjson是近年來一個爭議性很大的話題,本文無意討論框架選型的對錯,只關注遷移這件事中遇到的問題進行反思和思考。大家如果有想發表的看法,可以在評論區 理 性 討論。** > 本文閱讀大概需要:5分鐘 > > 碼字不易,歡迎關注我的個人公眾號:後端技術漫談 # 為何要放棄fastjson? 究其原因,**是fastjson漏洞頻發**,導致了公司內部需要頻繁的督促各業務線升級fastjson版本,來防止安全問題。 fastjson在2020年頻繁暴露安全漏洞,此漏洞可以繞過autoType開關來實現反序列化遠端程式碼執行並獲取伺服器訪問許可權。 從2019年7月份釋出的v1.2.59一直到2020年6月份釋出的 v1.2.71 ,每個版本的升級中都有關於AutoType的升級,涉及13個正式版本。 fastjson中與AutoType相關的版本歷史: ``` 1.2.59釋出,增強AutoType開啟時的安全性 fastjson 1.2.60釋出,增加了AutoType黑名單,修復拒絕服務安全問題 fastjson 1.2.61釋出,增加AutoType安全黑名單 fastjson 1.2.62釋出,增加AutoType黑名單、增強日期反序列化和JSONPath fastjson 1.2.66釋出,Bug修復安全加固,並且做安全加固,補充了AutoType黑名單 fastjson 1.2.67釋出,Bug修復安全加固,補充了AutoType黑名單 fastjson 1.2.68釋出,支援GEOJSON,補充了AutoType黑名單 1.2.69釋出,修復新發現高危AutoType開關繞過安全漏洞,補充了AutoType黑名單 1.2.70釋出,提升相容性,補充了AutoType黑名單 1.2.71釋出,補充安全黑名單,無新增利用,預防性補充 ``` 相比之下,其他的json框架,如Gson和Jackson,漏洞數量少很多,**高危漏洞也比較少**,這是公司想要替換框架的主要原因。 # fastjson替代方案 **本文主要討論Gson替換fastjson框架的實戰問題,所以在這裡不展開詳細討論各種json框架的優劣,只給出結論。** 經過評估,主要有Jackson和Gson兩種json框架放入考慮範圍內,與fastjson進行對比。 ## 三種json框架的特點 ### FastJson > 速度快 > > fastjson相對其他JSON庫的特點是快,從2011年fastjson釋出1.1.x版本之後,其效能從未被其他Java實現的JSON庫超越。 > > 使用廣泛 > > fastjson在阿里巴巴大規模使用,在數萬臺伺服器上部署,fastjson在業界被廣泛接受。在2012年被開源中國評選為最受歡迎的國產開源軟體之一。 > > 測試完備 > > fastjson有非常多的testcase,在1.2.11版本中,testcase超過3321個。每次釋出都會進行迴歸測試,保證質量穩定。 > > 使用簡單 > > fastjson的API十分簡潔。 ### Jackson > 容易使用 - jackson API提供了一個高層次外觀,以簡化常用的用例。 > > 無需建立對映 - API提供了預設的對映大部分物件序列化。 > > 效能高 - 快速,低記憶體佔用,適合大型物件圖表或系統。 > > 乾淨的JSON - jackson建立一個乾淨和緊湊的JSON結果,這是讓人很容易閱讀。 > > 不依賴 - 庫不需要任何其他的庫,除了JDK。 ### Gson > 提供一種機制,使得將Java物件轉換為JSON或相反如使用toString()以及構造器(工廠方法)一樣簡單。 > > 允許預先存在的不可變的物件轉換為JSON或與之相反。 > > 允許自定義物件的表現形式 > > 支援任意複雜的物件 > > 輸出輕量易讀的JSON ## 效能對比 同事撰寫的效能對比原始碼: https://github.com/zysrxx/json-comparison 本文不詳細討論效能的差異,畢竟這其中涉及了很多各個框架的實現思路和優化,所以只給出結論: > 1.序列化單物件效能Fastjson > Jackson > Gson,其中Fastjson和Jackson效能差距很小,Gson效能較差 > > 2.序列化大物件效能Jackson> Fastjson > Gson ,序列化大Json物件時Jackson> Gson > Fastjson,Jackson序列化大資料時效能優勢明顯 > > 3.反序列化單物件效能 Fastjson > Jackson > Gson , 效能差距較小 > > 4.反序列化大物件效能 Fastjson > Jackson > Gson , 效能差距較很小 ## 最終選擇方案 - Jackson適用於高效能場景,Gson適用於高安全性場景 - 對於新專案倉庫,不再使用fastjson。對於存量系統,考慮到Json更換成本,由以下幾種方案可選: - 專案未使用autoType功能,建議直接切換為非fastjson,如果切換成本較大,可以考慮繼續使用fastjson,關閉safemode。 - 業務使用了autoType功能,建議推進廢棄fastjson。 # 替換依賴注意事項 企業專案或者說大型專案的特點: - 程式碼結構複雜,團隊多人維護。 - 承擔重要線上業務,一旦出現嚴重bug會導致重大事故。 - 如果是老專案,可能缺少文件,不能隨意修改,牽一髮而動全身。 - 專案有很多開發分支,不斷在迭代上線。 所以對於大型專案,想要做到將底層的fastjson遷移到gson是一件複雜且痛苦的事情,其實對於其他依賴的替換,也都一樣。 **我總結了如下幾個在替換專案依賴過程中要特別重視的問題。** ## 謹慎,謹慎,再謹慎 再怎麼謹慎都不為過,如果你要更改的專案是非常重要的業務,那麼一旦犯下錯誤,代價是非常大的。並且,對於業務方和產品團隊來說,沒有新的功能上線,但是系統卻炸了,是一件“無法忍受”的事情。儘管你可能覺得很委屈,因為只有你或者你的團隊知道,雖然業務看上去沒變化,但是程式碼底層已經發生了翻天覆地的變化。 所以,謹慎點! ## 做好開發團隊和測試團隊的溝通 在依賴替換的過程中,需要做好專案的規劃,比如分模組替換,嚴格細分排期。 把前期規劃做好,開發和測試才能有條不紊的進行工作。 開發之間,需要提前溝通好開發注意事項,比如依賴版本問題,防止由多個開發同時修改程式碼,最後發現使用的版本不同,介面用法都不同這種很尷尬,並且要花額外時間處理的事情。 而對於測試,更要事先溝通好。一般來說,測試不會太在意這種對於業務沒有變化的技術專案,因為既不是優化速度,也不是新功能。但其實遷移涉及到了底層,很容易就出現BUG。要讓測試團隊瞭解更換專案依賴,是需要大量的測試時間投入的,成本不亞於新功能,讓他們儘量重視起來。 ## 做好迴歸/介面測試 上面說到測試團隊需要投入大量工時,這些工時主要都用在專案功能的整體迴歸上,也就是迴歸測試。 當然,不只是業務迴歸測試,如果有條件的話,要做介面迴歸測試。 **如果公司有介面管理平臺,那麼可以極大提高這種專案測試的效率。** 打個比方,在一個模組修改完成後,在測試環境(或者沙箱環境),部署一個線上版本,部署一個修改後的版本,直接將介面返回資料進行對比。一般來說是Json對比,網上也有很多的Json對比工具: https://www.sojson.com/ ## 考慮遷移前後的效能差異 正如上面描述的Gson和Fastjson效能對比,替換框架需要注意框架之間的效能差異,尤其是對於流量業務,也就是高併發專案,響應時間如果發生很大的變化會引起上下游的注意,導致一些額外的後果。 # 使用Gson替換Fastjson **這裡總結了兩種json框架常用的方法,貼出詳細的程式碼示例,幫助大家快速的上手Gson,無縫切換!** ## Json反序列化 ``` String jsonCase = "[{\"id\":10001,\"date\":1609316794600,\"name\":\"小明\"},{\"id\":10002,\"date\":1609316794600,\"name\":\"小李\"}]"; // fastjson JSONArray jsonArray = JSON.parseArray(jsonCase); System.out.println(jsonArray); System.out.println(jsonArray.getJSONObject(0).getString("name")); System.out.println(jsonArray.getJSONObject(1).getString("name")); // 輸出: // [{"date":1609316794600,"name":"小明","id":10001},{"date":1609316794600,"name":"小李","id":10002}] // 小明 // 小李 // Gson JsonArray jsonArrayGson = gson.fromJson(jsonCase, JsonArray.class); System.out.println(jsonArrayGson); System.out.println(jsonArrayGson.get(0).getAsJsonObject().get("name").getAsString()); System.out.println(jsonArrayGson.get(1).getAsJsonObject().get("name").getAsString()); // 輸出: // [{"id":10001,"date":1609316794600,"name":"小明"},{"id":10002,"date":1609316794600,"name":"小李"}] // 小明 // 小李 ``` 看得出,兩者區別主要在get各種型別上,Gson呼叫方法有所改變,但是變化不大。 那麼,來看下空物件反序列化會不會出現異常: ``` String jsonObjectEmptyCase = "{}"; // fastjson JSONObject jsonObjectEmpty = JSON.parseObject(jsonObjectEmptyCase); System.out.println(jsonObjectEmpty); System.out.println(jsonObjectEmpty.size()); // 輸出: // {} // 0 // Gson JsonObject jsonObjectGsonEmpty = gson.fromJson(jsonObjectEmptyCase, JsonObject.class); System.out.println(jsonObjectGsonEmpty); System.out.println(jsonObjectGsonEmpty.size()); // 輸出: // {} // 0 ``` 沒有異常,開心。 看看空陣列呢,畢竟[]感覺比{}更加容易出錯。 ``` String jsonArrayEmptyCase = "[]"; // fastjson JSONArray jsonArrayEmpty = JSON.parseArray(jsonArrayEmptyCase); System.out.println(jsonArrayEmpty); System.out.println(jsonArrayEmpty.size()); // 輸出: // [] // 0 // Gson JsonArray jsonArrayGsonEmpty = gson.fromJson(jsonArrayEmptyCase, JsonArray.class); System.out.println(jsonArrayGsonEmpty); System.out.println(jsonArrayGsonEmpty.size()); // 輸出: // [] // 0 ``` 兩個框架也都沒有問題,完美解析。 ## 範型處理 解析泛型是一個非常常用的功能,我們專案中大部分fastjson程式碼就是在解析json和Java Bean。 ``` // 實體類 User user = new User(); user.setId(1L); user.setUserName("馬雲"); // fastjson List