<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          fastjson牛逼!但我要把你干掉了

          共 10805字,需瀏覽 22分鐘

           ·

          2021-01-16 12:36


          本文公眾號來源:后端技術漫談

          作者:蠻三刀把刀

          本文已收錄至我的GitHub


          【對線面試官】系列?一周兩篇持續(xù)更新中!

          前言

          本篇文章是我這一個多月來幫助組內廢棄fastjson框架的總結,我們將大部分Java倉庫從fastjson遷移至了Gson。

          這么做的主要的原因是公司受夠了fastjson頻繁的安全漏洞問題,每一次出現漏洞都要推一次全公司的fastjson強制版本升級,很令公司頭疼。

          文章的前半部分,我會簡單分析各種json解析框架的優(yōu)劣,并給出企業(yè)級項目遷移json框架的幾種解決方案。

          在文章的后半部分,我會結合這一個月的經驗,總結下Gson的使用問題,以及fastjson遷移到Gson踩過的深坑。

          文章目錄:

          • 為何要放棄fastjson?
          • fastjson替代方案
            • 三種json框架的特點
            • 性能對比
            • 最終選擇方案
          • 替換依賴時的注意事項
            • 謹慎,謹慎,再謹慎
            • 做好開發(fā)團隊和測試團隊的溝通
            • 做好回歸/接口測試
            • 考慮遷移前后的性能差異
          • 使用Gson替換fastjson
            • Json反序列化
            • 范型處理
            • List/Map寫入
            • 駝峰與下劃線轉換
          • 遷移常見問題踩坑
            • Date序列化方式不同
            • SpringBoot異常
            • Swagger異常
            • @Mapping JsonObject作為入參異常

          注意:是否使用fastjson是近年來一個爭議性很大的話題,本文無意討論框架選型的對錯,只關注遷移這件事中遇到的問題進行反思和思考。大家如果有想發(fā)表的看法,可以在評論區(qū) 理 性 討論。

          為何要放棄fastjson?

          究其原因,是fastjson漏洞頻發(fā),導致了公司內部需要頻繁的督促各業(yè)務線升級fastjson版本,來防止安全問題。

          fastjson在2020年頻繁暴露安全漏洞,此漏洞可以繞過autoType開關來實現反序列化遠程代碼執(zhí)行并獲取服務器訪問權限。

          從2019年7月份發(fā)布的v1.2.59一直到2020年6月份發(fā)布的 v1.2.71 ,每個版本的升級中都有關于AutoType的升級,涉及13個正式版本。

          fastjson中與AutoType相關的版本歷史:

          1.2.59發(fā)布,增強AutoType打開時的安全性?fastjson
          1.2.60發(fā)布,增加了AutoType黑名單,修復拒絕服務安全問題?fastjson
          1.2.61發(fā)布,增加AutoType安全黑名單?fastjson
          1.2.62發(fā)布,增加AutoType黑名單、增強日期反序列化和JSONPath?fastjson
          1.2.66發(fā)布,Bug修復安全加固,并且做安全加固,補充了AutoType黑名單?fastjson
          1.2.67發(fā)布,Bug修復安全加固,補充了AutoType黑名單?fastjson
          1.2.68發(fā)布,支持GEOJSON,補充了AutoType黑名單
          1.2.69發(fā)布,修復新發(fā)現高危AutoType開關繞過安全漏洞,補充了AutoType黑名單
          1.2.70發(fā)布,提升兼容性,補充了AutoType黑名單
          1.2.71發(fā)布,補充安全黑名單,無新增利用,預防性補充

          相比之下,其他的json框架,如Gson和Jackson,漏洞數量少很多,高危漏洞也比較少,這是公司想要替換框架的主要原因。

          fastjson替代方案

          本文主要討論Gson替換fastjson框架的實戰(zhàn)問題,所以在這里不展開詳細討論各種json框架的優(yōu)劣,只給出結論。

          經過評估,主要有Jackson和Gson兩種json框架放入考慮范圍內,與fastjson進行對比。

          三種json框架的特點

          FastJson

          速度快

          fastjson相對其他JSON庫的特點是快,從2011年fastjson發(fā)布1.1.x版本之后,其性能從未被其他Java實現的JSON庫超越。

          使用廣泛

          fastjson在阿里巴巴大規(guī)模使用,在數萬臺服務器上部署,fastjson在業(yè)界被廣泛接受。在2012年被開源中國評選為最受歡迎的國產開源軟件之一。

          測試完備

          fastjson有非常多的testcase,在1.2.11版本中,testcase超過3321個。每次發(fā)布都會進行回歸測試,保證質量穩(wěn)定。

          使用簡單

          fastjson的API十分簡潔。

          Jackson

          容易使用 - jackson API提供了一個高層次外觀,以簡化常用的用例。

          無需創(chuàng)建映射 - API提供了默認的映射大部分對象序列化。

          性能高 - 快速,低內存占用,適合大型對象圖表或系統(tǒng)。

          干凈的JSON - jackson創(chuàng)建一個干凈和緊湊的JSON結果,這是讓人很容易閱讀。

          不依賴 - 庫不需要任何其他的庫,除了JDK。

          Gson

          提供一種機制,使得將Java對象轉換為JSON或相反如使用toString()以及構造器(工廠方法)一樣簡單。

          允許預先存在的不可變的對象轉換為JSON或與之相反。

          允許自定義對象的表現形式

          支持任意復雜的對象

          輸出輕量易讀的JSON

          性能對比

          性能對比源碼:

          https://github.com/zysrxx/json-comparison

          本文不詳細討論性能的差異,畢竟這其中涉及了很多各個框架的實現思路和優(yōu)化,所以只給出結論:

          1.序列化單對象性能Fastjson > Jackson > Gson,其中Fastjson和Jackson性能差距很小,Gson性能較差

          2.序列化大對象性能Jackson> Fastjson > Gson ,序列化大Json對象時Jackson> Gson > Fastjson,Jackson序列化大數據時性能優(yōu)勢明顯

          3.反序列化單對象性能 Fastjson > Jackson > Gson , 性能差距較小

          4.反序列化大對象性能 Fastjson > Jackson > Gson , 性能差距較很小

          最終選擇方案

          • Jackson適用于高性能場景,Gson適用于高安全性場景
          • 對于新項目倉庫,不再使用fastjson。對于存量系統(tǒng),考慮到Json更換成本,由以下幾種方案可選:
            • 項目未使用autoType功能,建議直接切換為非fastjson,如果切換成本較大,可以考慮繼續(xù)使用fastjson,關閉safemode。
            • 業(yè)務使用了autoType功能,建議推進廢棄fastjson。

          替換依賴注意事項

          企業(yè)項目或者說大型項目的特點:

          • 代碼結構復雜,團隊多人維護。
          • 承擔重要線上業(yè)務,一旦出現嚴重bug會導致重大事故。
          • 如果是老項目,可能缺少文檔,不能隨意修改,牽一發(fā)而動全身。
          • 項目有很多開發(fā)分支,不斷在迭代上線。

          所以對于大型項目,想要做到將底層的fastjson遷移到gson是一件復雜且痛苦的事情,其實對于其他依賴的替換,也都一樣。

          我總結了如下幾個在替換項目依賴過程中要特別重視的問題。

          謹慎,謹慎,再謹慎

          再怎么謹慎都不為過,如果你要更改的項目是非常重要的業(yè)務,那么一旦犯下錯誤,代價是非常大的。并且,對于業(yè)務方和產品團隊來說,沒有新的功能上線,但是系統(tǒng)卻炸了,是一件“無法忍受”的事情。盡管你可能覺得很委屈,因為只有你或者你的團隊知道,雖然業(yè)務看上去沒變化,但是代碼底層已經發(fā)生了翻天覆地的變化。

          所以,謹慎點!

          做好開發(fā)團隊和測試團隊的溝通

          在依賴替換的過程中,需要做好項目的規(guī)劃,比如分模塊替換,嚴格細分排期。

          把前期規(guī)劃做好,開發(fā)和測試才能有條不紊的進行工作。

          開發(fā)之間,需要提前溝通好開發(fā)注意事項,比如依賴版本問題,防止由多個開發(fā)同時修改代碼,最后發(fā)現使用的版本不同,接口用法都不同這種很尷尬,并且要花額外時間處理的事情。

          而對于測試,更要事先溝通好。一般來說,測試不會太在意這種對于業(yè)務沒有變化的技術項目,因為既不是優(yōu)化速度,也不是新功能。但其實遷移涉及到了底層,很容易就出現BUG。要讓測試團隊了解更換項目依賴,是需要大量的測試時間投入的,成本不亞于新功能,讓他們盡量重視起來。

          做好回歸/接口測試

          上面說到測試團隊需要投入大量工時,這些工時主要都用在項目功能的整體回歸上,也就是回歸測試。

          當然,不只是業(yè)務回歸測試,如果有條件的話,要做接口回歸測試。

          如果公司有接口管理平臺,那么可以極大提高這種項目測試的效率。

          打個比方,在一個模塊修改完成后,在測試環(huán)境(或者沙箱環(huán)境),部署一個線上版本,部署一個修改后的版本,直接將接口返回數據進行對比。一般來說是Json對比,網上也有很多的Json對比工具:

          https://www.sojson.com/

          考慮遷移前后的性能差異

          正如上面描述的Gson和Fastjson性能對比,替換框架需要注意框架之間的性能差異,尤其是對于流量業(yè)務,也就是高并發(fā)項目,響應時間如果發(fā)生很大的變化會引起上下游的注意,導致一些額外的后果。

          使用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":"小李"}]
          //?小明
          //?小李

          看得出,兩者區(qū)別主要在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?userListResultFastjson?=?JSONArray.parseArray(JSON.toJSONString(userList),?User.class);
          List?userListResultFastjson2?=?JSON.parseObject(JSON.toJSONString(userList),?new?TypeReference>(){});
          System.out.println(userListResultFastjson);
          System.out.println("userListResultFastjson2"?+?userListResultFastjson2);
          //?輸出:
          //?userListResultFastjson[User?[Hash?=?483422889,?id=1,?userName=馬云],?null]
          //?userListResultFastjson2[User?[Hash?=?488970385,?id=1,?userName=馬云],?null]

          //?Gson
          List?userListResultTrue?=?gson.fromJson(gson.toJson(userList),?new?TypeToken>(){}.getType());
          System.out.println("userListResultGson"?+?userListResultGson);
          //?輸出:
          //?userListResultGson[User?[Hash?=?1435804085,?id=1,?userName=馬云],?null]

          可以看出,Gson也能支持泛型。

          List/Map寫入

          這一點fastjson和Gson有區(qū)別,Gson不支持直接將List寫入value,而fastjson支持。

          所以Gson只能將List解析后,寫入value中,詳見如下代碼:

          //?實體類
          User?user?=?new?User();
          user.setId(1L);
          user.setUserName("馬云");

          //?fastjson
          JSONObject?jsonObject1?=?new?JSONObject();
          jsonObject1.put("user",?user);
          jsonObject1.put("userList",?userList);
          System.out.println(jsonObject1);
          //?輸出:
          //?{"userList":[{"id":1,"userName":"馬云"},null],"user":{"id":1,"userName":"馬云"}}

          //?Gson
          JsonObject?jsonObject?=?new?JsonObject();
          jsonObject.add("user",?gson.toJsonTree(user));
          System.out.println(jsonObject);
          //?輸出:
          //?{"user":{"id":1,"userName":"馬云"},"userList":[{"id":1,"userName":"馬云"},null]}

          如此一來,Gson看起來就沒有fastjson方便,因為放入List是以gson.toJsonTree(user)的形式放入的。這樣就不能先入對象,在后面修改該對象了。(有些同學比較習慣先放入對象,再修改對象,這樣的代碼就得改動)

          駝峰與下劃線轉換

          駝峰轉換下劃線依靠的是修改Gson的序列化模式,修改為LOWER_CASE_WITH_UNDERSCORES

          GsonBuilder?gsonBuilder?=?new?GsonBuilder();
          gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
          Gson?gsonUnderScore?=?gsonBuilder.create();
          System.out.println(gsonUnderScore.toJson(user));
          //?輸出:
          //?{"id":1,"user_name":"馬云"}

          常見問題排雷

          下面整理了我們在公司項目遷移Gson過程中,踩過的坑,這些坑現在寫起來感覺沒什么技術含量。但是這才是我寫這篇文章的初衷,幫助大家把這些很難發(fā)現的坑避開。

          這些問題有的是在測試進行回歸測試的時候發(fā)現的,有的是在自測的時候發(fā)現的,有的是在上線后發(fā)現的,比如Swagger掛了這種不會去測到的問題。

          Date序列化方式不同

          不知道大家想過一個問題沒有,如果你的項目里有緩存系統(tǒng),使用fastjson寫入的緩存,在你切換Gson后,需要用Gson解析出來。所以就一定要保證兩個框架解析邏輯是相同的,但是,顯然這個愿望是美好的。

          在測試過程中,發(fā)現了Date類型,在兩個框架里解析是不同的方式。

          • fastjson:Date直接解析為Unix
          • Gson:直接序列化為標準格式Date

          導致了Gson在反序列化這個json的時候,直接報錯,無法轉換為Date。

          解決方案:

          新建一個專門用于解析Date類型的類:

          import?com.google.gson.TypeAdapter;
          import?com.google.gson.stream.JsonReader;
          import?com.google.gson.stream.JsonWriter;

          import?java.io.IOException;
          import?java.util.Date;

          public?class?MyDateTypeAdapter?extends?TypeAdapter?{
          ????@Override
          ????public?void?write(JsonWriter?out,?Date?value)?throws?IOException?{
          ????????if?(value?==?null)?{
          ????????????out.nullValue();
          ????????}?else?{
          ????????????out.value(value.getTime());
          ????????}
          ????}

          ????@Override
          ????public?Date?read(JsonReader?in)?throws?IOException?{
          ????????if?(in?!=?null)?{
          ????????????return?new?Date(in.nextLong());
          ????????}?else?{
          ????????????return?null;
          ????????}
          ????}
          }

          接著,在創(chuàng)建Gson時,把他放入作為Date的專用處理類:

          Gson?gson?=?new?GsonBuilder().registerTypeAdapter(Date.class,new?MyDateTypeAdapter()).create();

          這樣就可以讓Gson將Date處理為Unix。

          當然,這只是為了兼容老的緩存,如果你覺得你的倉庫沒有這方面的顧慮,可以忽略這個問題。

          SpringBoot異常

          切換到Gson后,使用SpringBoot搭建的Web項目的接口直接請求不了了。報錯類似:

          org.springframework.http.converter.HttpMessageNotWritableException

          因為SpringBoot默認的Mapper是Jackson解析,我們切換為了Gson作為返回對象后,Jackson解析不了了。

          解決方案:

          application.properties里面添加:

          #Preferred?JSON?mapper?to?use?for?HTTP?message?conversion
          spring.mvc.converters.preferred-json-mapper=gson

          Swagger異常

          這個問題和上面的SpringBoot異常類似,是因為在SpringBoot中引入了Gson,導致 swagger 無法解析 json。

          采用類似下文的解決方案(添加Gson適配器):

          http://yuyublog.top/2018/09/03/springboot%E5%BC%95%E5%85%A5swagger/

          1. GsonSwaggerConfig.java
          @Configuration
          public?class?GsonSwaggerConfig?{
          ????//設置swagger支持gson
          ????@Bean
          ????public?IGsonHttpMessageConverter?IGsonHttpMessageConverter()?{
          ????????return?new?IGsonHttpMessageConverter();
          ????}
          }
          1. IGsonHttpMessageConverter.java
          public?class?IGsonHttpMessageConverter?extends?GsonHttpMessageConverter?{
          ????public?IGsonHttpMessageConverter()?{
          ????????//自定義Gson適配器
          ????????super.setGson(new?GsonBuilder()
          ????????????????.registerTypeAdapter(Json.class,?new?SpringfoxJsonToGsonAdapter())
          ????????????????.serializeNulls()//空值也參與序列化
          ????????????????.create());
          ????}
          }
          1. SpringfoxJsonToGsonAdapter.java
          public?class?SpringfoxJsonToGsonAdapter?implements?JsonSerializer?{
          ????@Override
          ????public?JsonElement?serialize(Json?json,?Type?type,?JsonSerializationContext?jsonSerializationContext)?{
          ????????return?new?JsonParser().parse(json.value());
          ????}
          }

          @Mapping JsonObject作為入參異常

          有時候,我們會在入參使用類似:

          public?ResponseResult?submitAudit(@RequestBody?JsonObject?jsonObject)?{}

          如果使用這種代碼,其實就是使用Gson來解析json字符串。但是這種寫法的風險是很高的,平常請大家盡量避免使用JsonObject直接接受參數。

          在Gson中,JsonObject若是有數字字段,會統(tǒng)一序列化為double,也就是會把count = 0這種序列化成count = 0.0

          為何會有這種情況?簡單的來說就是Gson在將json解析為Object類型時,會默認將數字類型使用double轉換。

          如果Json對應的是Object類型,最終會解析為Map類型;其中Object類型跟Json中具體的值有關,比如雙引號的""值翻譯為STRING。我們可以看下數值類型(NUMBER)全部轉換為了Double類型,所以就有了我們之前的問題,整型數據被翻譯為了Double類型,比如30變?yōu)榱?0.0。

          可以看下Gson的ObjectTypeAdaptor類,它繼承了Gson的TypeAdaptor抽象類:

          具體的源碼分析和原理闡述,大家可以看這篇拓展閱讀:

          https://www.jianshu.com/p/eafce9689e7d

          解決方案:

          第一個方案:把入參用實體類接收,不要使用JsonObject

          第二個方案:與上面的解決Date類型問題類似,自己定義一個Adaptor,來接受數字,并且處理。這種想法我覺得可行但是難度較大,可能會影響到別的類型的解析,需要在設計適配器的時候格外注意。

          總結

          這篇文章主要是為了那些需要將項目遷移到Gson框架的同學們準備的。

          一般來說,個人小項目,是不需要費這么大精力去做遷移,所以這篇文章可能目標人群比較狹窄。

          但文章中也提到了不少通用問題的解決思路,比如怎么評估遷移框架的必要性。其中需要考慮到框架兼容性,兩者性能差異,遷移耗費的工時等很多問題。

          希望文章對你有所幫助。

          歡迎關注我的微信公眾號【面試造火箭】來聊聊Java面試

          添加我的私人微信sanwaiyihao進一步交流和學習

          【對線面試官】系列?一周兩篇持續(xù)更新中!

          瀏覽 103
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美在线观看网址 | 9.1成人黄~A片 | 国产又爽 又黄 免费网站免费观看 | 91CM229 跳蛋购物 突袭做爱 1080P - 美竹玲 | 欧美一级在线观看 |