前言
大家好,又雙叒叕見面了,我是天天放大家鴿子的蠻三刀。
在被大家取關之前,我立下一個“遠大的理想”,一定要在這周更新文章。現在看來,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在2020年頻繁暴露安全漏洞,此漏洞可以繞過autoType開關來實作反序列化遠端代碼執行并擷取伺服器通路權限。
從2019年7月份釋出的v1.2.59一直到2020年6月份釋出的 v1.2.71 ,每個版本的更新中都有關于AutoType的更新,涉及13個正式版本。
fastjson中與AutoType相關的版本曆史:
相比之下,其他的json架構,如Gson和Jackson,漏洞數量少很多,高危漏洞也比較少,這是公司想要替換架構的主要原因。
本文主要讨論Gson替換fastjson架構的實戰問題,是以在這裡不展開詳細讨論各種json架構的優劣,隻給出結論。
經過評估,主要有Jackson和Gson兩種json架構放入考慮範圍内,與fastjson進行對比。
速度快 fastjson相對其他JSON庫的特點是快,從2011年fastjson釋出1.1.x版本之後,其性能從未被其他Java實作的JSON庫超越。 使用廣泛 fastjson在阿裡巴巴大規模使用,在數萬台伺服器上部署,fastjson在業界被廣泛接受。在2012年被開源中國評選為最受歡迎的國産開源軟體之一。 測試完備 fastjson有非常多的testcase,在1.2.11版本中,testcase超過3321個。每次釋出都會進行回歸測試,保證品質穩定。 使用簡單 fastjson的API十分簡潔。
容易使用 - jackson API提供了一個高層次外觀,以簡化常用的用例。 無需建立映射 - API提供了預設的映射大部分對象序列化。 性能高 - 快速,低記憶體占用,适合大型對象圖表或系統。 幹淨的JSON - jackson建立一個幹淨和緊湊的JSON結果,這是讓人很容易閱讀。 不依賴 - 庫不需要任何其他的庫,除了JDK。
提供一種機制,使得将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,無縫切換!
看得出,兩者差別主要在get各種類型上,Gson調用方法有所改變,但是變化不大。
那麼,來看下空對象反序列化會不會出現異常:
沒有異常,開心。
看看空數組呢,畢竟[]感覺比{}更加容易出錯。
兩個架構也都沒有問題,完美解析。
解析泛型是一個非常常用的功能,我們項目中大部分fastjson代碼就是在解析json和Java Bean。
可以看出,Gson也能支援泛型。
這一點fastjson和Gson有差別,Gson不支援直接将List寫入value,而fastjson支援。
是以Gson隻能将List解析後,寫入value中,詳見如下代碼:
如此一來,Gson看起來就沒有fastjson友善,因為放入List是以gson.toJsonTree(user)的形式放入的。這樣就不能先入對象,在後面修改該對象了。(有些同學比較習慣先放入對象,再修改對象,這樣的代碼就得改動)
駝峰轉換下劃線依靠的是修改Gson的序列化模式,修改為LOWER_CASE_WITH_UNDERSCORES
常見問題排雷
下面整理了我們在公司項目遷移Gson過程中,踩過的坑,這些坑現在寫起來感覺沒什麼技術含量。但是這才是我寫這篇文章的初衷,幫助大家把這些很難發現的坑避開。
這些問題有的是在測試進行回歸測試的時候發現的,有的是在自測的時候發現的,有的是在上線後發現的,比如Swagger挂了這種不會去測到的問題。
不知道大家想過一個問題沒有,如果你的項目裡有緩存系統,使用fastjson寫入的緩存,在你切換Gson後,需要用Gson解析出來。是以就一定要保證兩個架構解析邏輯是相同的,但是,顯然這個願望是美好的。
在測試過程中,發現了Date類型,在兩個架構裡解析是不同的方式。
fastjson:Date直接解析為Unix
Gson:直接序列化為标準格式Date
導緻了Gson在反序列化這個json的時候,直接報錯,無法轉換為Date。
解決方案:
建立一個專門用于解析Date類型的類:
接着,在建立Gson時,把他放入作為Date的專用處理類:
這樣就可以讓Gson将Date處理為Unix。
當然,這隻是為了相容老的緩存,如果你覺得你的倉庫沒有這方面的顧慮,可以忽略這個問題。
切換到Gson後,使用SpringBoot搭建的Web項目的接口直接請求不了了。報錯類似:
因為SpringBoot預設的Mapper是Jackson解析,我們切換為了Gson作為傳回對象後,Jackson解析不了了。
application.properties裡面添加:
這個問題和上面的SpringBoot異常類似,是因為在SpringBoot中引入了Gson,導緻 swagger 無法解析 json。
采用類似下文的解決方案(添加Gson擴充卡):
GsonSwaggerConfig.java
IGsonHttpMessageConverter.java
SpringfoxJsonToGsonAdapter.java
有時候,我們會在入參使用類似:
如果使用這種代碼,其實就是使用Gson來解析json字元串。但是這種寫法的風險是很高的,平常請大家盡量避免使用JsonObject直接接受參數。
在Gson中,JsonObject若是有數字字段,會統一序列化為double,也就是會把count = 0這種序列化成count = 0.0。
為何會有這種情況?簡單的來說就是Gson在将json解析為Object類型時,會預設将數字類型使用double轉換。
如果Json對應的是Object類型,最終會解析為Map<String, Object>類型;其中Object類型跟Json中具體的值有關,比如雙引号的""值翻譯為STRING。我們可以看下數值類型(NUMBER)全部轉換為了Double類型,是以就有了我們之前的問題,整型資料被翻譯為了Double類型,比如30變為了30.0。
可以看下Gson的ObjectTypeAdaptor類,它繼承了Gson的TypeAdaptor抽象類:
具體的源碼分析和原理闡述,大家可以看這篇拓展閱讀:
https://www.jianshu.com/p/eafce9689e7d
第一個方案:把入參用實體類接收,不要使用JsonObject
第二個方案:與上面的解決Date類型問題類似,自己定義一個Adaptor,來接受數字,并且處理。這種想法我覺得可行但是難度較大,可能會影響到别的類型的解析,需要在設計擴充卡的時候格外注意。
總結
這篇文章主要是為了那些需要将項目遷移到Gson架構的同學們準備的。
一般來說,個人小項目,是不需要費這麼大精力去做遷移,是以這篇文章可能目标人群比較狹窄。
但文章中也提到了不少通用問題的解決思路,比如怎麼評估遷移架構的必要性。其中需要考慮到架構相容性,兩者性能差異,遷移耗費的工時等很多問題。
希望文章對你有所幫助。