Prologue
对于一个应用程序来说,保存数据几乎是一个必备的功能。当程序退出执行前,我们往往需要将一些关键的数据保存在硬盘/存储卡中,以供下次使用。通常,这样一个过程我们也称之为数据的持久化(persistence)。
在 Java 中,实现数据持久化的方式是序列化(serialization)与反序列化。
Serialization
那么,什么是序列化呢? 简单来说,序列化就是将一个对象转化成(字节)流的过程。经序列化后的对象变成了流,也就是一串由 0101 组成的字节,我们就可以将其保存在文件系统当中,即使程序退出也不会导致数据的丢失。
当然,我们对数据的保存,最终的目的肯定是在某个时刻要将其重新拿来使用,为此,就需要有反序列化,读取一个流,并将其重新转化成一个对象。很明显,我们可以看出,序列化和反序列化事实上是两个完全”对称”的操作,因此,它就需要满足一个最基本的特征——确定性。对于两个完全相同的对象,我们要求每次序列化得到的字节数据应该是完全一样的。
在 Java 中,实现序列化的方式很简单,我们只需要实现 Serializable 接口即可。事实上,Serializable 是一个标记接口,即只用于标识作用的空接口,类似的还有 Clonable 和 Remote。实现该接口后,我们只需要使用 wirteObject 和 readObject 就可以使用 ObjectInputStream 的 readObject() 还有 ObjectOutputStream 的 writeObject(Object obj) 方法来实现持久化。示例如下:
1 | import java.io.*; |
注意到,当变量被标记为 static 或者 transient 的时候,并不会被持久化。static 变量是属于类的,因此没有持久化的必要。而 transient 关键字则是用于另一些特殊情形,比如变量可以通过其他数据计算得到,或者处于安全考虑等等。使用 transient 关键字的变量在 readObject 后会被赋默认值(整型为 0,类则为 null 等)。
如果不是很清楚的话,不妨运行一下上面那段代码,并看一下代码所得到的输出是什么。可以看到,最后类 A 中只有变量 a 能被持久化。
Json
在传输数据的时候,尤其是在客户端和服务器之间传输数据的时候,我们往往需要用到这样一个东西,叫 json。
JSON 全称是 JavaScript Object Notation,从名称可以看出,它最初是用于 JavaScript 这门语言当中的,后面经过各种各样的拓展,现在已经成为了一种应用广泛的数据存储格式,各种编程语言中往往会提供各式各样的库去生成或者解析 JSON 文件/字符串了。JSON 能支持 null, string, number, boolean, array, JSON object 这些数据类型。常见的 JSON 文件大概是这个样子(来自维基百科):
1 | { |
可以看到,JSON 本身其实就是一个一个的键值对,并且,JSON 对象之间还可以相互嵌套,从这个角度上说,和常见的 HTML,XML 等格式还是一定的相似之处。
Gson
在 Android 开发过程中,遇到需要和后端进行数据交互的时候,数据之间的传输往往就是 JSON 格式,为此,Google 爸爸为我们提供了一个开源的库叫 Gson,用于进行 Json 的解析和生成。
Gson 本身的用法比较简单,给我们两个非常直接的方法 fromJson 和 toJson 来帮助我们实现 Java 的数据类和 Json 格式之间的转换。由于其用法本身十分简单,这里不进行介绍,今天,我们尝试着一起来看一下,Gson 这个库究竟是怎么实现的。
首先,还是先看一下 Gson 是怎样进行解析的吧。
fromJson
首先,点进 fromJson 方法。注意,这里有参数为 Type 的类型,这是 Java 中所有类型(包括基本类型)都实现的接口,位于反射相关的包当中。
可以看到,fromJson 有很多个重载,最后所有的重载都调用了一个方法:
1 | public <T> T fromJson(JsonReader reader, Type typeOfT) |
其中,JsonReader 是 Gson 库中对 Reader 的一个包装,我们可以先把它简单的看成是一个可以读取 Json 字符串的黑匣子即可。typeOfT 即泛型变量的类型。在这个函数中,最关键的几行为:
1 | try { |
由于上面用到了 TypeToken 和 TypeAdapter 两个类,我们需要先来看一下这两个类究竟在做一些什么。
TypeToken
顾名思义,TypeToken 这个类的出现,主要是为了解决泛型的类型擦除问题。利用反射的方式,获得泛型的信息。至于类型擦除出现的原因,如果不太清楚的话建议先去了解一下再继续看。
通常,在解析含有泛型信息的类的时候,我们会类似这样写:
1 | List<String> model = gson.fromJson(jsonStr, new TypeToken<List<String>>(){}.getType()); |
TypeToken 中的变量如下:
1 | public class TypeToken<T> { |
type 变量表示 T 的实际类型(包含泛型信息),rowType 变量则表示 T 泛型擦除后的信息。比如 TypeToken<String>(){} 的 type 和 rawType 均为 java.lang.String 而 TypeToken<List<String>>(){} 的 type 为 java.util.List<java.lang.String>,而 rawType 为 interface java.util.List。
捕获泛型信息的关键是 Class 类的 Type getGenericSuperclass() 方法,该方法可以获得某个类的父类的类型信息(包含泛型信息)。而为了获得父类的类型信息,我们使用匿名内部类来实现。
利用这个原理,我们可以这样来实现一个最基本的 TypeToken:
1 | class TypeToken<T> { |
TypeAdapter
解决完 TypeToken 后,接下来我们来看一下 TypeAdapter 是做什么的。
点进 TypeAdapter 类,我们可以看到,这是一个抽象类,包含两个抽象方法(以及许多其他相关的非抽象方法,这里忽略):
1 | public abstract class TypeAdapter<T> { |
可以看到,这个类就是 Gson 实现序列化和反序列化的核心了。回到一开始的 fromJson 中的几行关键代码,整个 Deserialization 的流程就可以简化为:
- 根据所给的字符串,创建一个 JsonReader 类,用于读取字符串中的数据(或自己提供一个 JsonReader)
- 将我们给的类强转为 Type,并使用 TypeToken 捕获包含泛型信息在内的类型信息
- 使用创建的 TypeToken,创建 TypeAdapter,提供 Type 和其对应的 Java 类的一个适配。同时这里我们也可以知道,Type 和 TypeAdapter 存在一一对应的映射关系
- 进行适配操作(read),得到一个类型为 Type 的 Java 对象
因此,接下来,我们只需要弄清楚 TypeAdapter 是在干什么就可以了。上文中,TypeAdapter 是 getAdapter 方法返回的对象,这里,我们可以来简单看一下这个方法:
1 | public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) { |
可以看到,TypeAdapter 应该是由 TypeAdapterFactory 这个类(接口)生成的,这是一种典型的工产模式。接口为:
1 | public interface TypeAdapterFactory { |
通过查找 factories,我们可以看到,这是一个在 Gson 对象的构造函数里面创建的一个 unmodifiableList。构造方法里,直接”暴力”地添加了所有类型的对象对应的 Adapter,然后在 create 方法中,如果 type 和对应的 Factory 是匹配的,就可以返回。(这里其实甚至可以添加用户自定义的 Factory,且优先级是很高的)
实际使用 Gson 的时候,我们需要反序列化的通常都是自己定义的数据类,定位到 TypeAdapterFactory,对应的就是 ReflectiveTypeAdapterFactory。这里我们不妨看一下它对应的工产方法。
1 | public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { |
前面几行判断 type 是不是原始类型,是的话就有问题(Type 和 Factory不对应),返回空。下面的 ConstructorConsturctor 从字面意思看也很明显,就是构造器的构造器,给定一个 Type 变量,返回一个 Type 类型的构造器,具体的实现方式就是反射,这里不再进入细节。后面一行就是创建 Adapter 了。这里又用到了一个 getBoundFields 方法。
首先是 BoundField 类,这个类比较简单,一个 BoundField 和一个内部的变量相对应,用于判断每个变量是否需要进行序列化和反序列化。getBoundFields 返回一个 Map,即所有的变量和其对应的 BoundField 的集合。
其次是 Adapter 类,这是 ReflectiveTypeAdapterFactory 的一个静态内部类,实现了 read 和 write 方法。这样,我们只需要在 read 中一一读取各个变量,然后调用 BoundField 的 read 方法,读取变量并赋值。最后再把 instance 返回,就完成了整个解析的过程了。
至于其他类型的反序列化过程,做法是类似的,可以通过查看对应的工厂类方法查看。这里不再赘述。
1 | public T read(JsonReader in) throws IOException { |
至于 toJson 方法,实现方法相对更简单一点,而且也不是这篇文章的中心,因此这里就不再细写。
Epilogue
这篇文章主要简单地介绍了 Java 的序列化,以及简单地介绍 gson 库的实现,总的来说,gson 主要是使用反射来实现反序列化的。有几个比较注意的点,一个是使用反射获取包含泛型信息的类的信息,另一个是使用 TypeAdapter 实现 Type 和对应的 Java Bean 的映射,并使用反射,获得构造器并创建对象,最后依次对各个变量进行赋值,递归进行则可以得到我们要的对象。不过一个需要注意的问题是,由于需要用到反射,这样的写法效率上还是有些低的。
参考资料:
https://blog.csdn.net/jiangjiajian2008/article/details/53314084