當前位置: 妍妍網 > 碼農

線上告警炸鍋!FastJson 又立功了。。

2024-04-30碼農

前段時間新增一個特別簡單的功能,晚上上線前review程式碼時想到公司拼搏進取的價值觀臨時加一行log日誌,覺得就一行簡單的日誌基本上沒啥問題,結果剛上完線後一堆報警,趕緊回滾了程式碼,找到問題刪除了添加日誌的程式碼,重新上線完畢。

情景還原

定義了一個 CountryDTO

public classCountryDTO{private String country;public void setCountry(String country) {this.country = country; }public String getCountry() {returnthis.country; }publicBoolean isChinaName() {returnthis.country.equals("中國"); }}

定義測試類 FastJonTest

public classFastJonTest { @TestpublicvoidtestSerialize() { CountryDTO countryDTO = new CountryDTO(); String str = JSON.toJSONString(countryDTO); System.out.println(str); }}

執行時報空指標錯誤:

透過報錯資訊可以看出來是 序列化的過程中執行了 isChinaName()方法,這時候this.country變量為空, 那麽問題來了:

  • 序列化為什麽會執行isChinaName()呢?

  • 引申一下,序列化過程中會執行那些方法呢?

  • 源分碼析

    透過debug觀察呼叫鏈路的堆疊資訊

    呼叫鏈中的ASMSerializer_1_CountryDTO.write是FastJson使用asm技術動態生成了一個類ASMSerializer_1_CountryDTO,

    asm技術其中一項使用場景就是透過到動態生成類用來代替java反射,從而避免重復執行時的反射開銷

    JavaBeanSerizlier序列化原理

    透過下圖看出序列化的過程中,主要是呼叫JavaBeanSerializer類的write()方法。

    而JavaBeanSerializer 主要是透過 getObjectWriter()方法獲取,透過對getObjectWriter()執行過程的偵錯,找到比較關鍵的com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer方法,進而找到 com.alibaba.fastjson.util.TypeUtils#computeGetters

    public static List<FieldInfo> computeGetters( class<?> clazz, // JSONType jsonType, // Map<String,String> aliasMap, // Map<String,Field> fieldCacheMap, // boolean sorted, // PropertyNamingStrategy propertyNamingStrategy // ){//省略部份程式碼.... Method[] methods = clazz.getMethods();for(Method method : methods){//省略部份程式碼...if(method.getReturnType().equals(Void.TYPE)){continue; }if(method.getParameterTypes().length != 0){continue; }//省略部份程式碼... JSONField annotation = TypeUtils.getAnnotation(method, JSONField. class);//省略部份程式碼...if(annotation != null){if(!annotation.serialize()){continue; }if(annotation.name().length() != 0){//省略部份程式碼... } }if(methodName.startsWith("get")){//省略部份程式碼... }if(methodName.startsWith("is")){//省略部份程式碼... } }}

    從程式碼中大致分為三種情況:

  • @JSONField(.serialize = false, name = "xxx")註解

  • getXxx() : get開頭的方法

  • isXxx():is開頭的方法

  • 序列化流程圖

    範例程式碼

    /** * case1: @JSONField(serialize = false) * case2: getXxx()返回值為void * case3: isXxx()返回值不等於布爾型別 * case4: @JSONType(ignores = "xxx") */@JSONType(ignores = "otherName")public classCountryDTO {private String country;publicvoidsetCountry(String country) {this.country = country; }public String getCountry() {returnthis.country; }publicstaticvoidqueryCountryList() { System.out.println("queryCountryList()執行!!"); }public Boolean isChinaName() { System.out.println("isChinaName()執行!!");returntrue; }public String getEnglishName() { System.out.println("getEnglishName()執行!!");return"lucy"; }public String getOtherName() { System.out.println("getOtherName()執行!!");return"lucy"; }/** * case1: @JSONField(serialize = false) */ @JSONField(serialize = false)public String getEnglishName2() { System.out.println("getEnglishName2()執行!!");return"lucy"; }/** * case2: getXxx()返回值為void */publicvoidgetEnglishName3() { System.out.println("getEnglishName3()執行!!"); }/** * case3: isXxx()返回值不等於布爾型別 */public String isChinaName2() { System.out.println("isChinaName2()執行!!");return"isChinaName2"; }}

    執行結果為:

    isChinaName()執行!!getEnglishName()執行!!{"chinaName":true,"englishName":"lucy"}

    程式碼規範

    可以看出來序列化的規則還是很多的,比如有時需要關註返回值,有時需要關註參數個數,有時需要關註@JSONType註解,有時需要關註@JSONField註解;當一個事物的判別方式有多種的時候,由於團隊人員掌握知識點的程度不一樣,這個變異數很容易導致程式碼問題,所以盡量有一種推薦方案。這裏推薦使用@JSONField(serialize = false)來顯式的標註方法不參與序列化,下面是使用推薦方案後的程式碼,是不是一眼就能看出來哪些方法不需要參與序列化了。

    public classCountryDTO{private String country;public void setCountry(String country) {this.country = country; }public String getCountry() {returnthis.country; }@JSONField(serialize = false)public static void queryCountryList() { System.out.println("queryCountryList()執行!!"); }publicBoolean isChinaName() { System.out.println("isChinaName()執行!!");returntrue; }public String getEnglishName() { System.out.println("getEnglishName()執行!!");return"lucy"; }@JSONField(serialize = false)public String getOtherName() { System.out.println("getOtherName()執行!!");return"lucy"; }@JSONField(serialize = false)public String getEnglishName2() { System.out.println("getEnglishName2()執行!!");return"lucy"; }@JSONField(serialize = false)public void getEnglishName3() { System.out.println("getEnglishName3()執行!!"); }@JSONField(serialize = false)public String isChinaName2() { System.out.println("isChinaName2()執行!!");return"isChinaName2"; }}

    三個頻率高的序列化的情況

    以上流程基本遵循 發現問題 --> 原理分析 --> 解決問題 --> 昇華(編程規範)。

    圍繞業務上:解決問題 -> 如何選擇一種好的額解決方案 -> 好的解決方式如何擴充套件n個系統套用;

    圍繞技術上:解決單個問題,順著單個問題掌握這條線上的原理。

    來源:網路

    >>

    END

    精品資料,超贊福利,免費領

    微信掃碼/長按辨識 添加【技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

    👇👇

    👇點選"閱讀原文",獲取更多資料(持續更新中