学习Gson序列化源码简单记录
简单按照源码实现下「 List to JsonStr」。直接实现也很简单,不过为了锻炼下阅读源码的能力,还是练习跟踪下源码,找出大体框架,不沉迷细节,把握整体。
Gson的使用
环境约定
这里使用的 Java 环境,使用 Maven 管理依赖
- Oracle JDK8

Gson2.8.6
1
2
3
4
5<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
使用
1 | import com.google.gson.Gson; |

跟踪过程
找到的第一个抽象write方法
- 查看
gson.toJson(students)的源码,位于Gson.java

- 这里面很明显, 615 行代码不会执行,转向 618 行的源码,位于
Gson.java。

这时候一看就是知道是 638 行代码执行起的序列化作用,该行源代码同样位于
Gson.java
依旧点进去 683 行的源代码,位于
Gson.java
一看代码有点多,但是主要部分还是容易看出来的

所以可以直接跳到 704 行代码,位于
TypeAdapter.java
从类名带
Adapter来看,运用了适配器模式,这方面这里不展开这里是抽象方法,我们不知道会调用了哪个实现了它的方法,所以我们可以打上断点去调试,然后开启调试
找到的第二个抽象write方法
根据上面开启调试,会立马定位到该方法,位于
CollectionTypeAdapterFactory.java
为了搞明白,我们依旧得利用调试去查看每个方法的作用,首先在 95 行断点,按照这里来看,序列化的内容应该会输出在
out这个变量里面,目前out变量里面的内容:
经过 95 行后,变成

可见
out.beginArray();和out.endArray();只是在序列化字符串前后加个[ ],我们直接模拟实现即可那么重点应该是 96 那里了,我们可以看
collection变量里面的内容
里面是我们之前创建的三个
Student对象,那么自然 97 行代码也就是对每个对象进行序列化了点进去后同样跳到了抽象的
write方法,位于TypeAdapter.java,用同样的加断点方法进行调试
找到的第三个抽象write方法
根据上面调试,跳到如下位置,位于
TypeAdapterRuntimeTypeWrapper.java
这时候我们可以选择直接断点在 69 行,查看序列化字符串变化情况。现在断点在 53 行的情况:

待程序执行到 69 行,
out变量里面的内容依旧没有变化
那么可以确定序列化代码在 69 行了。
点进 69 行代码,位于
TypeAdapter.java,如下:
这时候我们可以看到其实每次调用的这些的抽象
write方法都是一样的,但是实现类却不一样,每个都有自己的功能。这个过程就好像套娃一样,老千层饼了那么依旧断点在 127 行这个抽象的
write方法,为了避免其他的断点的干扰,以及方便跳到我们想要的地方,我们可以把其他断点去掉先,选择后按delete键可以删除

然后我们再给 127 行打上断点

当然上面只是一种方法,要是你觉得这种方法比较麻烦,你可以直接按下面的红色小箭头直接进入源码,省去断点在接口的麻烦,但是这样就略去了接口的细节

FAQ:为什么不点蓝色箭头?
因为蓝色那个有的地方跳不进去
2020 years later
根据上面,跳到
ObjectTypeAdapter.java
这时候我们可以判断程序有没有执行 101 行里面的内容,
Step over F8走下去,发现并没有进去,那么也就说,又到了我们最喜欢的抽象write方法环节了,emmm,点进去一如既往:
为了方便,就不调试断点,直接
Step into进去进到
ReflectiveTypeAdapterFactory.java
同样我们需要确定 240 行的作用,先查看现在
out变量的内容:
执行完 240 行后

可以看到 240 行作用只不过是添加个
{而已,但是绝对不是直接添加{那么简单自然 251 行作用就是添加个
}然后接着
step over执行到 244 行,为了确定 244 行作用,我们选择的做法依旧是先执行查看结果后再决定要不要step into进去。这里 244 行并没有什么变化,反而是执行了 245 行后有变化了

找到序列化属性名的代码
进入到 245 行后的代码,位于
ReflectiveTypeAdapterFactory.java
同样为了确定主要代码部分,先让程序执行到 127 行

那么就可以确定是 127 行代码是主要部分,跳进去,又是一次套娃,emmm,这个我们在「找到第三个抽象write方法」那里就有出现过

这里我们选择的是
step over一步步进行,同时查看out方法的变化step over后,发现程序执行到 69 行out变量还没有变化,那么同样使用step into进去 69 行后,位于TypeAdapters.java,断点调试这行语句
发现
out变量内容由[{变成了[{"id":2020,那么自然得进去看这个代码调试 233 行,源码进入到
JsonWriter.java
待执行完 526 行,序列化字符串内容添加了个
"id"
简单分析写入属性名
调试 526 行代码,源码位于
JsonWriter.java
这里可以看到
defferredName的值为id,那么 401 行代码自然就是将其写入到序列化字符串中
这里是有做了字符串替换处理,会将一些特殊字符转换为unicode编程格式。
replacements正是一系列代替规则,htmlSafe是默认为true,替换规则默认如下:
写入序列化字符串的过程,比如 592 行,使用了
synchronized进行加锁,lock变量是个Object的实例

htmlSafe是在哪里置为true的
我们先在原地方JsonWrite.java查看htmlSafe变量的使用情况

发现有个htmlSafe属性,我们把其他的断点情况,将断点断在属性上面,运行debug


可以看到是有地方调了setHtmlSafe方法将其置为true
查看该方法的使用情况,发现在Gson.java有地方调用了该方法

碰巧第 700 行有个htmlSafe变量,接着一样是查看该变量使用情况,然后定位到Gson.java也有个htmlSafe的变量,同样断点该变量

发现这里有个构造函数,给该变量赋值为true,正好是使用默认构造函数创建Gson对象的时候会将htmlSafe变为true


找到序列化属性值的代码
上面我们看到,在JsonWrite.java里面的 526 行,作用是序列化上属性名,我们接着执行
执行到 532 行代码,这个函数作用是给序列化字符串加个”:”

那么,当我们执行玩 533 行时,就已经序列化好了属性名和属性值

是如何获取属性名和属性值的
在上面,我们在 521 行这个函数中得到了一个value值,这个是属性值,但是它是怎么得到的呢
我们可以查看这个函数的调用链:


从第一个函数来看,看不到什么
当我们查看第二个函数时,发现有个deferredName可疑,于是debug一下,正好这个值就是属性名,那么接下来我们需要得知它是如何被赋值的

Find Usages后,发现是个JsonWrite的一个属性,于是我们按照之前的方法给属性断点
然后断点停在了JsonWriter.java里

很显然,我们需要知道这个name如何来,查看调用链

这里找到一个比较可疑的变量boundField,位于ReflectiveTypeAdapterFactory.java,之所以说是可疑,因为它对比其他的变量特征显著,起码类型不是泛型,而是个具体的。
一看,在 201 行有被用到

查看 201 行那个函数的使用情况,找到如下


这里看到,有好几处运用到了反射,比如 152 166等行,再结合反射特点,所以推断出应该是用反射获得到属性值和属性名的
简单实现
1 | import java.lang.reflect.InvocationTargetException; |