Menu
快讀
  • 旅遊
  • 生活
    • 美食
    • 寵物
    • 養生
    • 親子
  • 娛樂
    • 動漫
  • 時尚
  • 社會
  • 探索
  • 故事
  • 科技
  • 軍事
  • 国际
快讀

FastJson稍微使用不當就會導致StackOverflow

2021 年 3 月 11 日 小鱼学护肤

對于廣大的開發人員來說,FastJson大家一定都不陌生。

FastJson(https://github.com/alibaba/fastjson)是阿裏巴巴的開源JSON解析庫,它可以解析JSON格式的字符串,支持將Java Bean序列化爲JSON字符串,也可以從JSON字符串反序列化到JavaBean。

FastJson稍微使用不當就會導致StackOverflow

可以看到,運行以上測試代碼後,代碼執行時,抛出了StackOverflow。

從以上截圖中異常的堆棧我們可以看到,主要是在執行到BuyerInfo的getJsonString方法後導致的。

那麽,爲什麽會發生這樣的問題呢?這就和FastJson的實現原理有關了。

FastJson的實現原理

關于序列化和反序列化的基礎知識大家可以參考Java對象的序列化與反序列化,這裏不再贅述。

FastJson的序列化過程,就是把一個內存中的Java Bean轉換成JSON字符串,得到字符串之後就可以通過數據庫等方式進行持久化了。

那麽,FastJson是如何把一個Java Bean轉換成字符串的呢,一個Java Bean中有很多屬性和方法,哪些屬性要保留,哪些要剔除呢,到底遵循什麽樣的原則呢?

其實,對于JSON框架來說,想要把一個Java對象轉換成字符串,可以有兩種選擇:

  • 1、基于屬性。
  • 2、基于setter/getter

關于Java Bean中的getter/setter方法的定義其實是有明確的規定的,參考JavaBeans(TM) Specification

而我們所常用的JSON序列化框架中,FastJson和jackson在把對象序列化成json字符串的時候,是通過遍曆出該類中的所有getter方法進行的。Gson並不是這麽做的,他是通過反射遍曆該類中的所有屬性,並把其值序列化成json。

不同的框架進行不同的選擇是有著不同的思考的,這個大家如果感興趣,後續文字可以專門介紹下。

那麽,我們接下來深入一下源碼,驗證下到底是不是這麽回事。

分析問題的時候,最好的辦法就是沿著異常的堆棧信息,一點點看下去。我們再來回頭看看之前異常的堆棧:

FastJson稍微使用不當就會導致StackOverflow

之所以使用ASM技術,主要是FastJson想通過動態生成類來避免重複執行時的反射開銷。但是,在FastJson中,兩種序列化實現是並存的,並不是所有情況都需要通過ASM生成一個動態類。讀者可以嘗試將BuyerInfo作爲一個內部類,重新運行以上Demo,再看異常堆棧,就會發現JavaBeanSerizlier的身影。

那麽,既然是因爲出現了循環調用導致了StackOverflowError,我們接下來就將重點放在爲什麽會出現循環調用上。

JavaBeanSerizlier序列化原理

我們已經知道,在FastJson序列化的過程中,會使用JavaBeanSerizlier進行,那麽就來看下 JavaBeanSerizlier到底做了什麽,他是如何幫助FastJson進行序列化的。

FastJson在序列化的過程中,會調用JavaBeanSerizlier的write方法進行,我們看一下這個方法的內容:

以上代碼,我們省略了大部分代碼之後,可以看到邏輯相對簡單:就是先獲取要序列化的對象的所有getter方法,然後遍曆方法進行執行,視圖通過getter方法獲得對應的屬性的值。

但是,當調用到我們定義的getJsonString方法的時候,進而會調用到JSON.toJSONString(this),就會再次調用到JavaBeanSerizlier的write。如此往複,形成死循環,進而發生StackOverflowError。

所以,如果你定義了一個Java對象,定一個了一個getXXX方法,並且在該方法中調用了JSON.toJSONString方法,那麽就會發生StackOverflowError!

如何避免StackOverflowError

通過查看FastJson的源碼,我們已經基本定位到問題了,那麽如何避免這個問題呢?

還是從源碼入手,既然JavaBeanSerizlier的write方法會嘗試獲取對象的所有getter方法,那麽我們就來看下他到底是怎麽獲取getter方法的,到底哪些方法會被他識別爲”getter”,然後我們再對症下藥。

在JavaBeanSerizlier的write方法中,getters的獲取方式如下:

可見,無論是this.sortedGetters還是this.getters,都是JavaBeanSerizlier中的屬性,那麽就繼續往上找,看看JavaBeanSerizlier是如何被初始化的。

通過調用棧追根溯源,我們可以發現,JavaBeanSerizlier是在SerializeConfig的成員變量serializers中獲取到的,那麽繼續深入,就要看SerializeConfig是如何被初始化的,即BuyerInfo對應的JavaBeanSerizlier是如何被塞進serializers的。

通過調用關系,我們發現,SerializeConfig.serializers是通過SerializeConfig.putInternal方法塞值的:

FastJson稍微使用不當就會導致StackOverflow

那麽,通過上圖,我們可以看到computeGetters方法在過濾getter方法的時候,是有一定的邏輯的,只要我們想辦法利用這些邏輯,就可以避免發生StackOverflowError。

這裏要提一句,下面將要介紹的幾種方法,都是想辦法使目標方法不參與序列化的,所以要特別注意下。但是話又說回來,誰會讓一個JavaBean的toJSONString進行序列化呢?

1、修改方法名

首先我們可以通過修改方法名的方式解決這個問題,我們把getJsonString方法的名字改一下,只要不以get開頭就可以了,如改爲toJsonString。

2、使用JSONField注解

除了修改方法名以外,FastJson還提供了兩個注解可以讓我們使用,首先介紹JSONField注解,這個注解可以作用在方法上,如果其參數serialize設置成false,那麽這個方法就不會被識別爲getter方法,就不會參加序列化。

3、使用JSONType注解

FastJson還提供了另外一個注解——JSONType,這個注解用于修飾類,可以指定ignores和includes。如下面的例子,如果使用@JSONType(ignores = “jsonString”)定義BuyerInfo,則也可避免StackOverflowError。

總結

FastJson是使用非常廣泛的序列化框架,可以在JSON字符串和Java Bean之間進行互相轉換。

但是在使用時要尤其注意,不要在Java Bean的getXXX方法中調用JSON.toJSONString方法,否則會導致StackOverflowError。

原因是因爲FastJson在序列化的時候,會根據一系列規則獲取一個對象中的所有getter方法,然後依次執行。

如果一定要定義一個方法,調用JSON.toJSONString的話,想要避免這個問題,可以采用以下方法:

  • 1、方法名不以get開頭
  • 2、使用@JSONField(serialize = false)修飾目標方法
  • 3、使用@JSONType修飾該Bean,並ignore掉方法對應的屬性名(getXxx -> xxx)

最後,作者之所以寫這篇文章,是因爲在工作中真的實實在在的碰到了這個問題。

發生問題的時候,我立刻想到改個方法名,把getJsonString改成了toJsonString解決了這個問題。因爲我之前看到過關于FastJson的簡單原理。

後來想著,既然FastJson設計成通過getter來進行序列化,那麽他一定提供了一個口子,讓開發者可以指定某些以get開頭的方法不參與序列化。

第一時間想到一般這種口子都是通過注解來實現的,于是打開FastJson的源代碼,找到了對應的注解。

然後,趁著周末的時間,好好的翻了一下FastJson的源代碼,徹底弄清楚了其底層的真正原理。

以上就是我 發現問題——>分析問題——>解決問題——>問題的升華 的全過程,希望對你有幫助。

通過這件事,筆者悟出了一個道理:

看過了太多的開發規範,卻依然還是會寫BUG!

希望通過這樣一篇小文章,可以讓你對這個問題有個基本的印象。

相關文章:

  • 2019 NLP大全:論文、博客、教程、工程進展全梳理(長文預警)
  • Tendermint Core Golang應用開發教程「含源代碼」
  • Alibaba Talent Programme 2022
  • Alibaba Cloud Becomes First Cloud Provider to Secure All…
  • GitHub 十大頂級 JavaScript 開源項目
  • Olympic Broadcasting Services Hosted in the Cloud for the…
探索

發佈留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

©2026 快讀 | 服務協議 | DMCA | 聯繫我們