<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本文主要講述了一個具有"隨機性"的反序列化錯誤!
Fastjson作為一款高效能的JSON序列化框架,使用場景眾多,不過也存在一些潛在的bug和不足。本文主要講述了一個具有"隨機性"的反序列化錯誤!
為了清晰地描述整個報錯的來龍去脈,將相關程式碼貼出來,同時也為了可以本地執行,看一下實際效果。
package test; import java.util.List; public class StewardTipItem { private Integer type; private List<String> contents; public StewardTipItem(Integer type, List<String> contents) { this.type = type; this.contents = contents; } }
反序列化時失敗,此類有兩個特殊之處:
package test; import java.util.ArrayList; import java.util.List; import java.util.Map; public class StewardTipCategory { private String category; private List<StewardTipItem> items; public StewardTipCategory build() { return null; } //C1 下文使用C1參照該建構函式 public StewardTipCategory(String category, Map<Integer,List<String>> items) { List<StewardTipItem> categoryItems = new ArrayList<>(); for (Map.Entry<Integer, List<String>> item : items.entrySet()) { StewardTipItem tipItem = new StewardTipItem(item.getKey(), item.getValue()); categoryItems.add(tipItem); } this.items = categoryItems; this.category = category; } // C2 下文使用C2參照該建構函式 public StewardTipCategory(String category, List<StewardTipItem> items) { this.category = category; this.items = items; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public List<StewardTipItem> getItems() { return items; } public void setItems(List<StewardTipItem> items) { this.items = items; } }
package test; import java.util.ArrayList; import java.util.List; import java.util.Map; public class StewardTip { private List<StewardTipCategory> categories; public StewardTip(Map<String, Map<Integer, List<String>>> categories) { List<StewardTipCategory> tipCategories = new ArrayList<>(); for (Map.Entry<String, Map<Integer, List<String>>> category : categories.entrySet()) { StewardTipCategory tipCategory = new StewardTipCategory(category.getKey(), category.getValue()); tipCategories.add(tipCategory); } this.categories = tipCategories; } public StewardTip(List<StewardTipCategory> categories) { this.categories = categories; } public List<StewardTipCategory> getCategories() { return categories; } public void setCategories(List<StewardTipCategory> categories) { this.categories = categories; } }
{ "categories":[ { "category":"工藝類", "items":[ { "contents":[ "工藝類-提醒項-內容1", "工藝類-提醒項-內容2" ], "type":1 }, { "contents":[ "工藝類-疑問項-內容1" ], "type":2 } ] } ] }
package test; import com.alibaba.fastjson.JSONObject; public class FastJSONTest { public static void main(String[] args) { String tip = "{"categories":[{"category":"工藝類","items":[{"contents":["工藝類-提醒項-內容1","工藝類-提醒項-內容2"],"type":1},{"contents":["工藝類-疑問項-內容1"],"type":2}]}]}"; try { JSONObject.parseObject(tip, StewardTip.class); } catch (Exception e) { e.printStackTrace(); } } }
當執行FastJSONTest的main方法時報錯:
com.alibaba.fastjson.JSONException: syntax error, expect {, actual [ at com.alibaba.fastjson.parser.deserializer.MapDeserializer.parseMap(MapDeserializer.java:228) at com.alibaba.fastjson.parser.deserializer.MapDeserializer.deserialze(MapDeserializer.java:67) at com.alibaba.fastjson.parser.deserializer.MapDeserializer.deserialze(MapDeserializer.java:43) at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:85) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:838) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:288) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:284) at com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer.parseArray(ArrayListTypeFieldDeserializer.java:181) at com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer.parseField(ArrayListTypeFieldDeserializer.java:69) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:838) at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:288) at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:672) at com.alibaba.fastjson.JSON.parseObject(JSON.java:396) at com.alibaba.fastjson.JSON.parseObject(JSON.java:300) at com.alibaba.fastjson.JSON.parseObject(JSON.java:573) at test.FastJSONTest.main(FastJSONTest.java:17)
排查過程有兩個難點:
經過多次執行之後還是找到了一些蛛絲馬跡!下面結合原始碼對整個過程進行簡單地敘述,最後也會給出怎麼能在報錯的時候debug到程式碼的方法。
clazz是StewardTipCategory.class的情況下,提出以下兩個問題:
Q1:Constructor[] constructors陣列的返回值是什麼?
Q2:constructors陣列元素的順序是什麼?
參考java.lang.Class#getDeclaredConstructors的註釋,可得到A1:
public test.StewardTipCategory(java.lang.String,java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)『C1』
public test.StewardTipCategory(java.lang.String,java.util.List<test.StewardTipItem>)『C2』
build()方法,C1建構函式,C2建構函式三者在Java原始檔的順序決定了constructors陣列元素的順序!
下表是經過多次實驗得到的一組資料,因為是手動觸發,並且次數較少,所以不能保證100%的準確性,只是一種大概率事件。
java.lang.Class#getDeclaredConstructors底層實現是native getDeclaredConstructors0,JVM的這部分程式碼沒有去閱讀,所以目前無法解釋產生這種現象的原因。
前 | 中 | 後 | 陣列元素順序 |
---|---|---|---|
build() | C1 | C2 | 隨機 |
C1 | build() | C2 | C2,C1 |
C1 | C2 | build() | C2,C1 |
build() | C2 | C1 | 隨機 |
C2 | build() | C1 | C1,C2 |
C2 | C1 | build() | C1,C2 |
C1 | C2 | C2,C1 | |
C2 | C1 | C1,C2 |
正是因為java.lang.Class#getDeclaredConstructors返回陣列元素順序的隨機性,才導致反序列化失敗的隨機性!
[C1,C2]順序下探尋反序列化失敗時程式碼執行的路徑。
com.alibaba.fastjson.util.JavaBeanInfo#build()方法體程式碼量比較大,忽略執行路徑上的無關程式碼。
JavaBeanDeserializer兩個重要屬性:
private final FieldDeserializer[] fieldDeserializers;
protected final FieldDeserializer[] sortedFieldDeserializers;
反序列化test.StewardTipCategory#items時fieldDeserializers的詳細資訊。
com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializercom.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#fieldValueDeserilizer(屬性值null,執行時會根據fieldType獲取具體實現類)com.alibaba.fastjson.util.FieldInfo#fieldType(java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)
建立完成執行
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.lang.Object, int, int[])
com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.Class<?>, java.lang.reflect.Type)根據欄位型別設定
com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#fieldValueDeserilizer的具體實現類。
test.StewardTipCategory#items屬性的實際型別是List。
反序列化時根據C1建構函式得到的fieldValueDeserilizer的實現類是com.alibaba.fastjson.parser.deserializer.MapDeserializer。
執行
com.alibaba.fastjson.parser.deserializer.MapDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object)時報錯。
java.lang.Class#getDeclaredConstructors返回[C2,C1]順序,反序列化時根據C2建構函式得到的fieldValueDeserilizer的實現類是
com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer,反序列化成功。
package test; import com.alibaba.fastjson.JSONObject; import java.lang.reflect.Constructor; public class FastJSONTest { public static void main(String[] args) { Constructor<?>[] declaredConstructors = StewardTipCategory.class.getDeclaredConstructors(); // if true must fail! if ("public test.StewardTipCategory(java.lang.String,java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)".equals(declaredConstructors[0].toGenericString())) { String tip = "{"categories":[{"category":"工藝類","items":[{"contents":["工藝類-提醒項-內容1","工藝類-提醒項-內容2"],"type":1},{"contents":["工藝類-疑問項-內容1"],"type":2}]}]}"; try { JSONObject.parseObject(tip, StewardTip.class); } catch (Exception e) { e.printStackTrace(); } } } }
StewardTipCategory建構函式C1方法簽名明顯不是一個很好的選擇,方法體除了屬性賦值,還做了一些額外的型別/資料轉換,也應該儘量避免。
開發人員對於使用的技術與框架要有深入的研究,尤其是底層原理,不能停留在使用層面。一些不起眼的事情可能導致不可思議的問題:java.lang.Class#getDeclaredConstructors。
框架實現時要保持嚴謹,報錯資訊儘可能清晰明瞭,StewardTipCategory反序列化失敗的原因在於,fastjson只檢驗了屬性名稱,建構函式引數個數而沒有進一步校驗屬性型別。
<<重構:改善既有程式碼的設計>>提倡程式碼方法塊儘量短小精悍,Fastjson某些模組的方法過於臃腫。
以上就是Fastjson反序列化隨機性失敗範例詳解的詳細內容,更多關於Fastjson反序列化隨機性的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45