當前位置: 妍妍網 > 碼農

Fastjson已進入公司黑名單元件庫,新計畫不準使用~

2024-05-26碼農

大家好,我是磊哥。

這個略顯馬麗蘇的標題,各位看官將就著看吧。主要是怕被噴。fastjson真的很好,我用不用我喜不喜歡的,太不重要了,我只是覺得不適合我而已。

話說以前GSON用得好好的,同事極力推薦我使用Fastjson,說很快雲雲。盡管我們的系統根本感知不出來這點速度差異。

之前也聽說Fastjson爆出來什麽重大漏洞,但對我們基本沒什麽影響,所以這一點倒是沒什麽偏見。

然後在一個新計畫上,腦抽抽,把gson換成了fastjson,還把spring boot預設支持的jackson換成了fastjson。

然後就開始遇到了一些問題。先聲明,這真不是尬黑,為了文章效果,故意網上扒些黑料拼湊起來,本文所提到的問題,都來源於本人最近計畫的真實經歷。

如果你近期準備面試跳槽,建議在ddkk.com線上刷題,涵蓋 一萬+ 道 Java 面試題,幾乎覆蓋了所有主流技術面試題,還有市面上最全的技術五百套,精品系列教程,免費提供。

dateformat優先級

本來是一個風和日麗的下午,一個非常簡單的改動需求。介面返回的時間只需要年月日日期型別不需要分時秒。

因為我配置全域時間格式化為 yyyy-MM-dd HH:mmss ,於是我愉快的在javabean的內容上加了個註解。

@JSONField(format="yyyy-MM-dd")

本地測試一下,沒問題,送出到測試環境,搞定,完美。

然後就接到產品的疑問,改動呢?

我登上去看了一下,唉,沒改到啊,日期還是帶了分時秒。

我大意了啊,這麽小的改動,又是在測試環境,就沒加驗證。

那麽現在的直接問題是:fastjson關於時間配置在局部的配置沒有生效,使用的還是全域配置。

現象是,開發環境windows上沒有問題,測試環境linux上出現了問題。兩者有什麽區別呢?系統問題?

既然懷疑是兩個系統導致的問題,那麽就在idea裏模擬一下linux系統。在 VM options 添加 -Dos.name=linux

這不能完全模擬linux系統,只針對透過 System.getproperty("os.name") 來判斷當前系統做某些操作的時候有用。

透過這種方式沒復現。

我又想到了遠端偵錯。

一陣操作猛如虎,遠端偵錯倒是能進斷點,只是斷點進不了第三方jar包的源碼。等於白搞。

如果你近期準備面試跳槽,建議在ddkk.com線上刷題,涵蓋 一萬+ 道 Java 面試題,幾乎覆蓋了所有主流技術面試題,還有市面上最全的技術五百套,精品系列教程,免費提供。

得,還是回到源碼吧。拉下源碼,斷點,觀察 JSONSerializer 類,主要是 writeWithFormat 方法。沒有發現問題。

因為懷疑是系統導致的,在源碼中搜尋'linux''unix'關鍵字,沒有發現。斷點整個流程重點觀察了一下這部份也沒有發現問題。

突然在 JSONSerializer.dateFormatPattern 上發現了這段註釋。

這部份涉及到了調整dateformat的問題,重點在這個#1868,這通常是github的問題編號。

1、 對於開源計畫來說,解決了BUG,通常會把問題編號放到註釋裏面去前提是註釋有必要透過問題編號可以看到問題的前因後果;
2、 通常來說,對於github開源計畫都有issue區,拿著這個到編號直接到issue一搜就能搜到;
3、 但也有一些項級計畫,如spark,flink是沒有issue區的,它們的型別問題發現描述追蹤都使用jira平台;

如: https://issues.apache.org/jira/browse/SPARK-38349, 在送出PR的時候標題也嚴格按照[jira 編號][spark 子模組(如core/sql) title]的規則來。

所以拿著這個編號到issue區,不管有沒有issue區,也都可以直接到pullrequest區直接搜尋,就算PR標題裏沒有問題編號,PR描述肯定也是有的,只要是有嚴格PR流程的開源計畫。

所以這個問題在這裏

https://github.com/alibaba/fastjson/issues/1868

相應的PR在這裏

https://github.com/alibaba/fastjson/pull/2706

透過ISSUES描述的已知資訊,可以看出他遇到的問題跟我是一樣的,而這個問題早在2018年就提出了。但問題描述不太專業,沒有涉及到環境以及最重要的fastjson的版本問題。

而透過PR可知,這個問題最終在2020才解決,期間僅在ISSUES區提出的相同問題就有 #1868#1968 #2029 #24524個。

解決問題的版本為:1.2.72.

這個資訊很關鍵。我對照了我開發環境的版本,是高於1.2.72的,所以沒有出現測試環境的問題。

所以,柯南告訴我們,排除了所有可能性,剩下的哪怕再可笑,也是最終問題所在。

那就是,測試環境所用的fastjson版本是低於1.2.72的。

這種可能性是存在的,因為我們用的是maven打程式碼包,依賴包單獨存在。

我最終在測試環境的依賴包目錄下發現了兩個Fastjson包,果然不出所料,有一個1.2.53的低版本,它就是罪魁禍首。

所以,最終這個問題有相當大的程度是由於我們團隊自身問題引發的。但透過解決這個問題的過程也發現了一些有意思的情況。

首先,Fastjson在某一個版本為什麽會引發這個問題。它肯定是某個PR改出問題的,rv,testcase覆蓋沒有到位。

其次,從試圖解決這個問題的3個PR的時間線,分別在2018年,2019年,2020年。說明,fastjson這個計畫的contributor看起來有百來人,但其中過於依賴其中某1個或者某些主力人員。精力有限,某些優先級不那麽高的BUG只能放任。

同時這個計畫的榮譽感並沒有那麽高(或者叫並沒有那麽吸引高手),它並不是apache頂級計畫,要是其它諸如spark/flink、spring,哪怕是dubbo呢,很想象這些計畫會有一個並不算復雜的BUG懸而未決長達3年時間。在這些頂級開源計畫,大家都是拼了老命的想找些BUG來送出PR。

如果你近期準備面試跳槽,建議在ddkk.com線上刷題,涵蓋 一萬+ 道 Java 面試題,幾乎覆蓋了所有主流技術面試題,還有市面上最全的技術五百套,精品系列教程,免費提供。

當然,以上只是我個人的一點猜測。

復盤,遇到Fastjson的問題,一開始就應該奔著github的issues區,它大機率已經被前人踩坑了。

$ ref迴圈參照問題

public ResultBody test(){
List<Person> list = new ArrayList<>();
Person obj1 = new Person("張三"48);
list.add(obj1);
Person obj2 = new Person("李四"23);
list.add(obj2);
Person obj3 = new Person("王麻子"17);
list.add(obj3);
List<Person> young = list.stream().filter(e -> e.getAge() <= 45).collect(Collectors.toList());
List<Person> children = list.stream().filter(e -> e.getAge()< 18).collect(Collectors.toList());
HashMap map = new HashMap();
map.put("young", young);
map.put("children", children);
return ResultBody.success(map);
}



以上測試介面返回前端什麽?

{
"code":"200",
"message":"成功!",
"result":{
"young":[
{
"age":23,
"name":"李四"
},
{
"age":17,
"name":"王麻子"
}
],
"children":[
{"$ref":"$.result.young[1]"}
]
 }
}

我現在並不知道什麽迴圈參照檢測,這時候它是我的知識盲區。

此時,我觀察到的現象是,young和children兩個list物件中均參照指向了王麻子這個物件。然後,在第2次children參照的時候它在序列化的時候直接指向了第1個young裏相應物件參照。

當然遇到這個問題的時候,我在仔細觀察排除了非fastjson的問題以後,這次我學聰明了,我直接來到了github的issues區,搜尋 $ref

果然有很多同道中人,近150個問題,從時間上來看還挺新鮮。我點選了closed,既然關閉了,那肯定解決了吧。

我點進了closed區第一個問題,然後作者讓升級到fastjson2。???

如果我沒有理解錯,fastjson和fastjson2可不是兩個版本的區別,是兩個計畫也!據說API也有相容性問題。直接這樣升級過去,談何容易!

我覺得這也是個槽點,Fastjson好像並沒有一個穩定維護的版本,遇到問題總是在升級,升級的過程中也沒做好品質控制,又引入了新的問題。

還是在當前計畫尋求解決方法吧,哪怕升版本也好啊。

終於在另一個問題下面找到了問題所在以及解決方案。

https://github.com/alibaba/fastjson/issues/3643

我現在知道這是由於迴圈參照檢測引起的。透過設定 SerializerFeature.DisableCircularReferenceDetect 可以避免這個問題。

但是,我的程式碼其實並沒有迴圈參照啊,只是兩個子物件參照了同一個物件而已。這算什麽?誤傷嗎?

更重要的,一些控制權應該在使用者手裏?

比如,當前這個迴圈參照在序列化會出的問題,應該是使用者手動去開啟,而不是預設給使用者開啟。

在優先級上,全域應該關閉,在有迴圈參照的地方,讓使用者選擇局部開啟。

現在我的前端並沒有使用Fastjson,面對" $ref":"$.result.young[1] "這種文本,它能解析嗎?它不能呀。我測試了一下,好像使用fastjson也並不能解析回來:

註:經提醒,這裏應使用完整報文解析,經測試,確實可以。感謝提醒!

更可怕的問題是,剛好在測試環節有兩個子物件參照了同一個物件,被我提前發現了。如果測試環境沒有這樣的情況,在生產環境剛好遇到了呢?那就是生產事故了呀。

本來是一個挺好的設計點,能起到錦上添花的作用,但它卻可能暴雷,這是好心辦壞事。

同樣的,還有 SerializerFeature.WriteMapNullValue

如果一個欄位值為null,fastjson預設就不返回該欄位了。本來前後端約定好,如果為null就怎樣處理的邏輯,可能在生產環境中突然暴雷啊。

就像 WriteNullListAsEmpty 就很好,不錯的設計點,如果返回的list為null的時候,使用者可以選擇讓它序列化為[],但它也不是預設開啟的呀,給了使用者額外的選擇權,對吧。

總結

寫到這裏的時候,我是真心覺得fastjson有比競品有些特色的地方。這真不是為了所謂的客觀公正,非要負面寫多點,再搞點正面的。

為了寫文章,那肯定要去試驗,得把競品也拿出來測試一下,一測試發現並不是fastjson獨有的,尷尬!

如果你近期準備面試跳槽,建議在ddkk.com線上刷題,涵蓋 一萬+ 道 Java 面試題,幾乎覆蓋了所有主流技術面試題,還有市面上最全的技術五百套,精品系列教程,免費提供。

但我還是那句話,不管你信不信,對於開源計畫,特別是這樣一個廣泛使用的開源計畫,肯定有非常值得學習的地方。一個開源計畫,如果整天拿著顯微鏡去觀察,那肯定能找出不少毛病。

這裏稍微總結一下本文的資訊點。並不一定是某個具體BUG,而是透過這個BUG,解決這個BUG背後所展現出來的fastjson的資訊或趨勢。

1、 review、testcase覆蓋不是很到位;
2、 contributor看起來很多,但嚴重依賴主力人員而主力精力有限,某些優先級不那麽高的BUG只能放任;
3、 這個計畫的榮譽感並沒有那麽高,或者叫並沒有那麽吸引高手);
4、 有些功能點應該把控制主動權交給使用者,如 DisableCircularReferenceDetect WriteMapNullValue 等預設開啟非常容易導致線上暴雷;
5、 作者已經全面轉向fastjosn2,而且哪怕在這之前,對於fastjson沒有一個穩定維護的版本,不斷升級,不斷引入新問題;

最後說一句,fastjson1已經進入公司黑名單元件庫,新計畫一律不準使用~

🔥 磊哥私藏精品 熱門推薦 🔥