Serialization in Java & Gson

Prologue

  对于一个应用程序来说,保存数据几乎是一个必备的功能。当程序退出执行前,我们往往需要将一些关键的数据保存在硬盘/存储卡中,以供下次使用。通常,这样一个过程我们也称之为数据的持久化(persistence)。

  在 Java 中,实现数据持久化的方式是序列化(serialization)与反序列化。

Serialization

  那么,什么是序列化呢? 简单来说,序列化就是将一个对象转化成(字节)流的过程。经序列化后的对象变成了流,也就是一串由 0101 组成的字节,我们就可以将其保存在文件系统当中,即使程序退出也不会导致数据的丢失。

  当然,我们对数据的保存,最终的目的肯定是在某个时刻要将其重新拿来使用,为此,就需要有反序列化,读取一个流,并将其重新转化成一个对象。很明显,我们可以看出,序列化和反序列化事实上是两个完全”对称”的操作,因此,它就需要满足一个最基本的特征——确定性。对于两个完全相同的对象,我们要求每次序列化得到的字节数据应该是完全一样的。

  在 Java 中,实现序列化的方式很简单,我们只需要实现 Serializable 接口即可。事实上,Serializable 是一个标记接口,即只用于标识作用的空接口,类似的还有 Clonable 和 Remote。实现该接口后,我们只需要使用 wirteObject 和 readObject 就可以使用 ObjectInputStream 的 readObject() 还有 ObjectOutputStream 的 writeObject(Object obj) 方法来实现持久化。示例如下:

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
import java.io.*;
public class SerializeTest {
public static void main(String[] args) {
A a = new A(100, -1);
String fileName = "test.txt";
try {
FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(a);
oos.close();
fos.close();
}
catch(Exception e) {
}
A.b = 10;
try {
FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis);
A a0 = (A) ois.readObject();
print(a0);
} catch (Exception e) {
}
}
static void print(A a) {
System.out.println(a.a);
System.out.println(A.b);
System.out.println(a.c);
}
}

class A implements Serializable {
int a;
static int b = 1;
transient int c;

public A(int a, int c) {
this.a = a;
this.c = c;
}
}

  注意到,当变量被标记为 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
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
{
"firstName": "John",
"lastName": "Smith",
"isAlive": true,
"age": 27,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "office",
"number": "646 555-4567"
},
{
"type": "mobile",
"number": "123 456-7890"
}
],
"children": [],
"spouse": null
}

  可以看到,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
2
3
4
5
6
try {
...
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
...

  由于上面用到了 TypeToken 和 TypeAdapter 两个类,我们需要先来看一下这两个类究竟在做一些什么。

TypeToken

  顾名思义,TypeToken 这个类的出现,主要是为了解决泛型的类型擦除问题。利用反射的方式,获得泛型的信息。至于类型擦除出现的原因,如果不太清楚的话建议先去了解一下再继续看。
  
  通常,在解析含有泛型信息的类的时候,我们会类似这样写:

1
List<String> model = gson.fromJson(jsonStr, new TypeToken<List<String>>(){}.getType());

  TypeToken 中的变量如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TypeToken<T> {
final Class<? super T> rawType;
// Type 类位于 Java 反射包当中,Java 是所有类型的一个 "超接口"
// 可以是原始类型,包括 int 等,也可以是含有泛型信息的类
final Type type;
final int hashCode;

protected TypeToken() {
this.type = getSuperclassTypeParameter(getClass());
this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);
this.hashCode = type.hashCode();
}
...
}

  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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class TypeToken<T> {
final Type type;
protected TypeToken() {
this.type = getSuperclassTypeParameter(getClass());
}
Type getSuperclassTypeParameter(Class<?> subClass) {
Type superClass = subClass.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) superClass;
return parameterizedType.getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
public class Test {
public static void main(String[] args) {
TypeToken<List<String>> token = new TypeToken<List<String>>(){};
System.out.println(token.getType());
}
}

TypeAdapter

  解决完 TypeToken 后,接下来我们来看一下 TypeAdapter 是做什么的。

  点进 TypeAdapter 类,我们可以看到,这是一个抽象类,包含两个抽象方法(以及许多其他相关的非抽象方法,这里忽略):

1
2
3
4
public abstract class TypeAdapter<T> {
public abstract void write(JsonWriter out, T value) throws IOException;
public abstract T read(JsonReader in) throws IOException;
}

  可以看到,这个类就是 Gson 实现序列化和反序列化的核心了。回到一开始的 fromJson 中的几行关键代码,整个 Deserialization 的流程就可以简化为:

  1. 根据所给的字符串,创建一个 JsonReader 类,用于读取字符串中的数据(或自己提供一个 JsonReader)
  2. 将我们给的类强转为 Type,并使用 TypeToken 捕获包含泛型信息在内的类型信息
  3. 使用创建的 TypeToken,创建 TypeAdapter,提供 Type 和其对应的 Java 类的一个适配。同时这里我们也可以知道,Type 和 TypeAdapter 存在一一对应的映射关系
  4. 进行适配操作(read),得到一个类型为 Type 的 Java 对象

  因此,接下来,我们只需要弄清楚 TypeAdapter 是在干什么就可以了。上文中,TypeAdapter 是 getAdapter 方法返回的对象,这里,我们可以来简单看一下这个方法:

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
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
// 这里从名字就可以看出,这里是对 TypeAdapter 做的一个缓存
TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
if (cached != null) {
return (TypeAdapter<T>) cached;
}
// 这里的 calls 是一个属于线程的变量,因此可以避免多线程造成的问题
Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
boolean requiresThreadLocalCleanup = false;
if (threadCalls == null) {
threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
calls.set(threadCalls);
requiresThreadLocalCleanup = true;
}
// FutureTypeAdapter 顾名思义,它是用来表示一个正在进行当中的调用
// 是其他 TypeAdapter 的一个代理。作用是防止同一个 Type 的递归调用,
// 导致栈溢出。可以考虑一下类 A 中含有类 A 的情况。
// 这点很关键,要理解好,不太理解也可以先跳过
FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
}
try {
FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
threadCalls.put(type, call);
for (TypeAdapterFactory factory : factories) {
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
// 成功创建 TypeAdapter,设置代理,并添加缓存后返回
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
return candidate;
}
}
}
...
}

  可以看到,TypeAdapter 应该是由 TypeAdapterFactory 这个类(接口)生成的,这是一种典型的工产模式。接口为:

1
2
3
public interface TypeAdapterFactory {
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
}

  通过查找 factories,我们可以看到,这是一个在 Gson 对象的构造函数里面创建的一个 unmodifiableList。构造方法里,直接”暴力”地添加了所有类型的对象对应的 Adapter,然后在 create 方法中,如果 type 和对应的 Factory 是匹配的,就可以返回。(这里其实甚至可以添加用户自定义的 Factory,且优先级是很高的)

  实际使用 Gson 的时候,我们需要反序列化的通常都是自己定义的数据类,定位到 TypeAdapterFactory,对应的就是 ReflectiveTypeAdapterFactory。这里我们不妨看一下它对应的工产方法。

1
2
3
4
5
6
7
8
@Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}

  前面几行判断 type 是不是原始类型,是的话就有问题(Type 和 Factory不对应),返回空。下面的 ConstructorConsturctor 从字面意思看也很明显,就是构造器的构造器,给定一个 Type 变量,返回一个 Type 类型的构造器,具体的实现方式就是反射,这里不再进入细节。后面一行就是创建 Adapter 了。这里又用到了一个 getBoundFields 方法。

  首先是 BoundField 类,这个类比较简单,一个 BoundField 和一个内部的变量相对应,用于判断每个变量是否需要进行序列化和反序列化。getBoundFields 返回一个 Map,即所有的变量和其对应的 BoundField 的集合。

  其次是 Adapter 类,这是 ReflectiveTypeAdapterFactory 的一个静态内部类,实现了 read 和 write 方法。这样,我们只需要在 read 中一一读取各个变量,然后调用 BoundField 的 read 方法,读取变量并赋值。最后再把 instance 返回,就完成了整个解析的过程了。

  至于其他类型的反序列化过程,做法是类似的,可以通过查看对应的工厂类方法查看。这里不再赘述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override public T read(JsonReader in) throws IOException {
...
T instance = constructor.construct();
try {
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
field.read(in, instance);
}
}
}
...
in.endObject();
return instance;
}

  至于 toJson 方法,实现方法相对更简单一点,而且也不是这篇文章的中心,因此这里就不再细写。

Epilogue

  这篇文章主要简单地介绍了 Java 的序列化,以及简单地介绍 gson 库的实现,总的来说,gson 主要是使用反射来实现反序列化的。有几个比较注意的点,一个是使用反射获取包含泛型信息的类的信息,另一个是使用 TypeAdapter 实现 Type 和对应的 Java Bean 的映射,并使用反射,获得构造器并创建对象,最后依次对各个变量进行赋值,递归进行则可以得到我们要的对象。不过一个需要注意的问题是,由于需要用到反射,这样的写法效率上还是有些低的。

参考资料:

https://blog.csdn.net/jiangjiajian2008/article/details/53314084

------------- The artical is over Thanks for your reading -------------