Fastjson源码分析
1diot9 Lv4

前言

之前只是知道能够触发getter或setter,但是却不知道为什么,现在就来结合源码分析一下。

parse和parseObject的区别

这个其实相当于结论了,先总结一下,看源码的时候能够更清楚一点。

  • 就返回的类型比较:

parse返回的是反序列化的类本身,而parseObject返回的是JSONObject类。

也就是说parse(jsonString)返回Student类,parseObject(jsonString)返回JSONObject类。

不过parseObject(jsonString, Student.class)返回的就是Student类了。

  • 就setter方法调用上:

如果对应的字段存在setter方法,且该setter方法满足:非静态私有函数,返回类型为void或类本身,参数个数为一个。

那么parse和parseObject(这里可以有一个或多个参数)的执行结果一样,即,调用所有满足上述条件的setter方法。

  • 就getter方法调用上:

如果对应的字段存在getter方法,且该getter方法满足:非静态私有函数,返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong,无参数,并且该字段没有setter方法(不然在JavaBeanInfo.build()的时候会先放setter方法,导致getter方法放不进,后面会分析)。

那么parse和parseObject(这里需要有两个以上的参数,即不能只有jsonString)的执行结果一样,即,调用所有满足上述条件的getter方法。

parseObject(jsonString) 这种单参的情况会有所不同。除了像上面一样会调用符合条件的getter,还会再次调用一遍所有getter方法。这是因为parseObject(String text)会调用JSON.toJSON()。可以在getter方法上打个断点,然后再看前面的调用栈。

  • 小结一下:

parse和多参parseObject都会调用符合条件的setter和getter。

单参parseObject除了上面的,还会再调用一遍所有getter。

正式分析

参考代码

首先,确保你的实体类里存在一个只有getter方法的字段,不然后面的derializer会变成ASMDerializer,这样分析流程就不一样了。

我这里用的fastjson1.2.24

这里给出一个实体类的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.io.IOException;
import java.util.HashMap;
import java.util.Properties;

public class Student {
private String name;
private int age;
private String address;
private Properties properties;
private HashMap hashMap;

public Student() {
System.out.println("构造函数");
}

public HashMap getHashMap() {
System.out.println("getHashMap");
return hashMap;
}

public String getName() {
System.out.println("getName");
return name;
}

public void setName(String name) {
System.out.println("setName");
this.name = name;
}

public int getAge() {
System.out.println("getAge");
return age;
}

public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}

public String getAddress() {
System.out.println("getAddress");
return address;
}

public Properties getProperties() throws IOException {
System.out.println("getProperties");
Runtime.getRuntime().exec("calc");
return properties;
}

// public void setProperties(Properties properties) {
// System.out.println("setProperties");
// this.properties = properties;
// }
}

然后反序列化可以用这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.alibaba.fastjson.JSON;

public class FastjsonEasyPoC {
public static void main(String[] args){
String jsonString = "{\"@type\":\"Student\",\"age\":6," +
"\"name\":\"Drunkbaby\",\"address\":\"china\",\"properties\":{}, \"hashMap\": {}}";


// Object parseObject = JSON.parseObject(jsonString);
// System.out.println(parseObject);
// System.out.println(parseObject.getClass().getName());
// System.out.println("\n");
Object parse = JSON.parse(jsonString);
System.out.println(parse);
System.out.println(parse.getClass().getName());

}
}

这里先测试parse,parse和多参parseObject的结果是一样的。

调试流程

parse

我们直接在ParserConfig的526行打断点:

img

前面的流程不是很重要,不过还是建议自己看一遍,这里我结合调用栈稍微讲一下:

img

一开始就是不断parse,业务层层包装,解析json数据,最终我们这里的json数据需要get一个Deserializer去反序列化,最后找到的Deserializer是一个JavaBeanDeserializer,然后就要去build,即重构JavaBean的内容,包括有哪些setter方法,哪些字段,哪些getter方法。我们重点来看一下build的过程。

首先,获得json类中的Field,Method,DefaultConstructor,可以自己去看一下变量里的信息:注意,由于是getMethods,所以private方法无法获取。

img

接下来,是对Method的一个遍历,这个很重要:

img

先做四个判断,方法名长度大于4,方法不能是静态的,方法返回类型为void或返回类型是这个类本身(这里是Student),方法的参数数量为1,只有这样才不会continue。这些满足条件的setter其实就是我们一开始总结的。可以发现setName是符合条件的,继续步过:

img

这里需要满足方法开头为set,之后就会取出字段名,这里是name。

最后,通过add,把刚刚得到的方法名,字段名等信息包装成FieldInfo,并存入fieldList:

img

接下来就是循环重复,所以我们直接在下面打断点:

img

不过由于这里是get public static fields,我们的Student并没有,所以会直接跳到下一个循环:

img

这里跟第一个循环很像,只不过是针对getter的,条件其实就跟一开始总结的一样。

下面这一行很重要:

img

如果一开始已经把setter存入fieldList,那么同一个字段的getter方法就存不进fieldList了,这就是为什么一个字段必须只存在getter方法,他的getter方法才会被调用。

现在回到createJavaBeanDeserializer方法下:

img

这里properties属性只有getter方法,这样asmEnable才会被设置为false,这样才不会进入ASM机制。

接下来直接回到DefaultJSONParse.parseObject(),368行:

img

跟进这个deserialze,这里我直接在setter方法上打断点了,这样往前看调用栈就行:

img

这样就可以很容易看出为什么会调用getter,setter了,这里不多讲了。

同时也可以发现,parse和parseObject其实最终都会走到DefaultJSONParser.parseObject(),也就是都走到parseObject。

parseObject

来看看为什么单参parseObject会调用所有getter

把下面的注释掉就行。

img

在这里打个断点:

img

运行到新打的断点,我这里还是选择在Student上打断点,然后反过来看调用栈:

img

实际上还是用到了之前的FieldInfo,不过这里的获取序列化器的过程用到ASM机制,这个比较复杂,这里不做解释,感兴趣的可以在这里打断点去调试:

img

总结

这里主要分析了一下parse和parseObject在什么时候会调用getter,setter。也算是自己第一次分析组件源码吧,当时花了大半天。

总结一下fastjson反序列化的机制:

1、通过字符串的截取,将你指定的类加载为Class对象

2、尝试获取反序列化器,如果不是默认的几个基本数据类型,则会被当成普通的JavaBean,从而去创建JavaBean反序列化器

3、在创建JavaBean反序列化器的过程中,会构建JavaBeanInfo,里面会选取符合要求的Field及其对应方法,并将其添加到fieldList里,这是决定getter,setter调用的关键

4、获取到反序列化器后,开始反序列化过程,通过反射调用setter还原对象,并根据情况调用getter方法

5、如果是单参的parseObject,最后返回的就是jsonObject。由于jsonObject能通过toJSONString还原成json格式,所以JSON.toJSON里一定调用了getter方法。

参考

Java反序列化Fastjson篇05-写给自己看的一些源码深入分析 | Drunkbaby’s Blog

由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 108.4k 访客数 访问量