我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Fastjson反序列化随机性失败示例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Fastjson反序列化随机性失败示例详解

前言

本文主要讲述了一个具有"随机性"的反序列化错误!

Fastjson作为一款高性能的JSON序列化框架,使用场景众多,不过也存在一些潜在的bug和不足。本文主要讲述了一个具有"随机性"的反序列化错误!

问题代码

为了清晰地描述整个报错的来龙去脉,将相关代码贴出来,同时也为了可以本地执行,看一下实际效果。

StewardTipItem

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;
    }
}

StewardTipCategory

反序列化时失败,此类有两个特殊之处:

  • 返回StewardTipCategory的build方法(忽略返回null值)。
  • 构造函数『C1』Map<Integer, List> items参数与List items属性同名,但类型不同!
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; 
    }
}

StewardTip

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;
    }
}

JSON字符串

{
    "categories":[
        {
             "category":"工艺类",
             "items":[
                 {
                     "contents":[
                         "工艺类-提醒项-内容1", 
                         "工艺类-提醒项-内容2"
                     ],
                     "type":1
                }, 
                { 
                     "contents":[ 
                         "工艺类-疑问项-内容1" 
                     ], 
                     "type":2
                }
            ]
        }
    ]
}

FastJSONTest

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)

问题排查

排查过程有两个难点:

  • 不能根据报错信息得到异常时JSON字符串的key,position或者其他有价值的提示信息。
  • 报错并不是每次执行都会发生,存在随机性,执行十次可能报错两三次,没有统计失败率。

经过多次执行之后还是找到了一些蛛丝马迹!下面结合源码对整个过程进行简单地叙述,最后也会给出怎么能在报错的时候debug到代码的方法。

JavaBeanInfo:285行

clazz是StewardTipCategory.class的情况下,提出以下两个问题:

Q1:Constructor[] constructors数组的返回值是什么?

Q2:constructors数组元素的顺序是什么?

参考java.lang.Class#getDeclaredConstructors的注释,可得到A1:

  • 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』

  • A2

build()方法,C1构造函数,C2构造函数三者在Java源文件的顺序决定了constructors数组元素的顺序!

下表是经过多次实验得到的一组数据,因为是手动触发,并且次数较少,所以不能保证100%的准确性,只是一种大概率事件。

java.lang.Class#getDeclaredConstructors底层实现是native getDeclaredConstructors0,JVM的这部分代码没有去阅读,所以目前无法解释产生这种现象的原因。

数组元素顺序
build()C1C2随机
C1build()C2C2,C1
C1C2build()C2,C1
build()C2C1随机
C2build()C1C1,C2
C2C1build()C1,C2
C1 C2C2,C1
C2C1C1,C2 

正是因为java.lang.Class#getDeclaredConstructors返回数组元素顺序的随机性,才导致反序列化失败的随机性!

  • [C2,C1]反序列化成功!
  • [C1,C2]反序列化失败!

[C1,C2]顺序下探寻反序列化失败时代码执行的路径。

JavaBeanInfo:492行

com.alibaba.fastjson.util.JavaBeanInfo#build()方法体代码量比较大,忽略执行路径上的无关代码。\

  • [C1,C2]顺序下代码会执行到492行,并执行两次(StewardTipCategory#category, StewardTipCategory#items各执行一次)。
  • 结束后创建一个com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer。

JavaBeanDeserializer:49行

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[])

JavaBeanDeserializer:838行

DefaultFieldDeserializer:53行

com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.Class<?>, java.lang.reflect.Type)根据字段类型设置

com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#fieldValueDeserilizer的具体实现类。

DefaultFieldDeserializer:34行

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)时报错。

MapDeserializer:228行

JavaBeanDeserializer:838行

java.lang.Class#getDeclaredConstructors返回[C2,C1]顺序,反序列化时根据C2构造函数得到的fieldValueDeserilizer的实现类是

com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer,反序列化成功。

问题解决

代码

  • 删除C1构造函数,使用其他方式创建StewardTipCategory。
  • 修改C1构造函数参数名称,类型,避免误导Fastjson。

调试

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。

Fastjson

框架实现时要保持严谨,报错信息尽可能清晰明了,StewardTipCategory反序列化失败的原因在于,fastjson只检验了属性名称,构造函数参数个数而没有进一步校验属性类型。

<<重构:改善既有代码的设计>>提倡代码方法块尽量短小精悍,Fastjson某些模块的方法过于臃肿。

以上就是Fastjson反序列化随机性失败示例详解的详细内容,更多关于Fastjson反序列化随机性的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Fastjson反序列化随机性失败示例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Fastjson反序列化随机性失败示例详解

这篇文章主要为大家介绍了Fastjson反序列化随机性失败示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

fastjson序列化时间自定义格式示例详解

这篇文章主要为大家介绍了fastjson序列化时间自定义格式示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-18

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录