楔子
目前项目中使用的json序列号依赖都是fastjson,但是由于fastjson漏洞频发,决定一律切换为Google的Gson。本以为不过是序列化反序列化而已,两个接口搞定,但是却出现了含有泛型的对象在反序列化时,int被转为double的情况(详情和解决方案可以参考博客Gson格式转换Integer变为Double类型问题解决),为了防止再发生类似的情况,对Gson的源码进行了一点简单的分析。
序列化介绍
在分析源码之前,先总结一些序列化的知识。serialization是将一个对象转为byte stream的过程,通过serialization ,我们可以将对象信息存储到文件中或者通过网络在不同的平台之间传播。deserialization是完全与之相反的过程,从byte stream中重建对象。在java中,只有实现了Serializable接口的对象才能实现序列化,如果没有实现就会在序列化过程中抛出异常。如果在序列化的过程中想要忽略某些字段,可以将其声明为static或者标记为transient。对于实现了序列化接口的类来说,编译器会自动为其生成一个serialVersionUID,但是想象以下情况,如果对象信息被序列化并存储到文件,之后由于某种原因对对象信息进行了修改,例如增删字段,这时候想要从文件中读出并重建对象,就会抛出InvalidClassException,这是因为在修改对象之后,会重新生成一个serialVersionUID,这样JVM在对比对象信息的时候就会抛出异常。序列化支持扩展和定制,我们可以通过为java class提供以下两个方法:
// Custom serialization logic will allow us to have additional serialization logic on top of the default one e.g. encrypting object before serialization
private void writeObject(ObjectOutputStream oos) throws IOException {
// Any Custom logic
oos.defaultWriteObject(); // Calling the default serialization logic
// Any Custom logic
}
// Custom deserialization logic will allow us to have additional deserialization logic on top of the default one e.g. decrypting object after deserialization
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Any Custom logic
ois.defaultReadObject(); // Calling the default deserialization logic
// Any Custom logic
}
这两个方法只对JVM可见。而如果我们想要禁止序列化,只需要在这两个方法里面抛出异常即可
使用方法
基本用法
利用Gson进行序列化和反序列化的接口比较简单,以Object转json为例,有两个常用的方法:
public String toJson(Object src) {
if (src == null) {
return toJson(JsonNull.INSTANCE);
}
return toJson(src, src.getClass());
}
该方法适用于非泛型的对象序列化,由于java的类型擦除特性,在src.getClass()时会丢失泛型的类型信息。与之相对的适合包含泛型的对象序列化的方法为
public String toJson(Object src, Type typeOfSrc) {
StringWriter writer = new StringWriter();
toJson(src, typeOfSrc, writer);
return writer.toString();
}
例如,将数组转化为json,具体代码为
Gson gson = new Gson();
Type typeToSerialize = new TypeToken<Collection<Integer>[]>() {}.getType();
Collection<Integer>[] arrayOfCollection = new ArrayList[arraySize];
//initialize数组
String json = gson.toJson(arrayOfCollection, typeToSerialize);
个性化设置
Gson类本身给我们提供了一些默认的配置,当这些默认配置不能满足我们的需要时,可以使用GsonBuilder来进行个性化配置,下面对于几个常用的配置进行说明:
设置TypeAdapterFactory
Gson为实现序列化为基本的类实现了默认的adapterFactory,定义了序列化和反序列化的逻辑,当默认设置不能满足需要,我们可以注册自己的adapterFactory。例如,我们在序列化Enum时要将其小写,可以先实现一个LowercaseEnumTypeAdapterFactory,然后进行注册:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
//其他设置
Gson gson = builder.create();
//转化
设置TypeAdapter
设置adapter与设置adapterFactory同理,代码实现如下:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Point.class, new PointAdapter());
// if PointAdapter didn't check for nulls in its read/write methods, you should instead use
// builder.registerTypeAdapter(Point.class, new PointAdapter().nullSafe());
...
Gson gson = builder.create();
常用注解
设置别名@SerializedName
使用@SerializedName注解可以对需要序列化反序列化的对象字段设置别名,这个注解会覆盖掉GsonBuilder初始化Gson时设置的FieldNamingPolicy。
核心类
Gson
1. 初始化上下文
Gson类是序列化反序列化的上下文接口,保存着上下文的配置信息,而且是用户进行序列化和反序列化的入口。可以直接实例化Gson对象或者使用GsonBuilder对上下文配置:
Gson gson = new Gson();
以上实例化代码,会启用Gson的默认配置,其中最重要的是初始化TypeAdapterFactory的配置,源码如下:
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories,
List<TypeAdapterFactory> builderHierarchyFactories,
List<TypeAdapterFactory> factoriesToBeAdded) {
this.excluder = excluder;
···
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
···
// type adapters for basic platform types
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
···
factories.add(TypeAdapters.ENUM_FACTORY);
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
//初始化factories信息
this.factories = Collections.unmodifiableList(factories);
}
需要特别注意的是,Gson实例是线程安全的,可以在多个线程之间分享一个公共的实例。这样就可以避免每次使用都要进行耗时的init操作了。
2. 序列化
以下通过对Object进行序列化为例进行源码分析,反序列化是它的镜像,不再进行赘述。
序列化的核心代码如下:
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
//根据对象类型获取TypeAdapter
TypeAdapter<?> adapter = getAdapter(TypeToken.get(typeOfSrc));
boolean oldLenient = writer.isLenient();
writer.setLenient(true);
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setSerializeNulls(serializeNulls);
try {
//调用adapter接口write方法将对象信息写入数据流
((TypeAdapter<Object>) adapter).write(writer, src);
} catch (IOException e) {
throw new JsonIOException(e);
} catch (AssertionError e) {
AssertionError error = new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage());
error.initCause(e);
throw error;
} finally {
writer.setLenient(oldLenient);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
}
}
核心调用过程可以用以下的时序图表示:
其中核心方法包括获取与对象Type对应的TypeAdapter和使用adapter的write方法对对象进行序列化。
为提高性能,Gson类内部会维护一个对于adapter的缓存,这样第一次获取特定TypeToken的TypeAdapter之后就将其加入缓存列表之中(typeTokenCache),源码如下:
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
//Adapter的缓存,存在一个hashMap中Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<TypeToken<?>, TypeAdapter<?>>();
TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
if (cached != null) {
return (TypeAdapter<T>) cached;
}
//防止递归调用ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>> calls
// = new ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>>();
Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
boolean requiresThreadLocalCleanup = false;
if (threadCalls == null) {
threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
calls.set(threadCalls);
requiresThreadLocalCleanup = true;
}
// the key and value type parameters always agree
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) {
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
return candidate;
}
}
throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
} finally {
threadCalls.remove(type);
if (requiresThreadLocalCleanup) {
calls.remove();
}
}
}
TypeAdapter
通过适配器模式为不同Type的对象定义具体的json格式。Gson的jar包本身提供了很多标准的adapter,但是在特殊的情况下,也可以通过实现TypeAdapter的抽象方法来定义自己的适配器。TypeAdapter类有两个核心的抽象方法如下:
//用于定义序列化规则
public abstract void write(JsonWriter out, T value) throws IOException;
//用于定义反序列化规则
public abstract T read(JsonReader in) throws IOException;
TypeAdapterFactory
顶级接口,定义非常简单:
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
会返回传入类型的TypeAdapter,当传入的类型不支持时,就会返回null。根据Gson.java的定义,每种类型都只会调用一次factory的create方法,当同一个类型对应多个TypeAdapterFactory时,先放入factory列表的工厂会生效。返回的adapter是可以复用的,一般情况下会将比较费时的操作例如反射在create方法中进行操作。
TypeToken
Gson.java中明确定义,当序列化反序列化的对象含有泛型,应使用以下接口:
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException;
//example
List<String> abc = new Gson().fromJson(json, new TypeToken<List<String>>() {}.getType());
我们知道JDK5引入泛型之后,为了兼容历史版本,在JVM编译泛型之后会对类型造成擦除,那么TypeToken是如何保存泛型的Type信息的呢?
以上的example可以看出是通过使用匿名内部类来实现。跟踪源码可知,TypeToken中保存着以下信息:
//对于以上example,rawType = java.util.List
final Class<? super T> rawType;
//对于以上example,type = java.util.List<java.lang.String>
final Type type;
final int hashCode;
生成匿名内部类初始化以上字段的核心代码如下:
TypeToken(Type type) {
this.type = $Gson$Types.canonicalize($Gson$Preconditions.checkNotNull(type));
this.rawType = (Class<? super T>) $Gson$Types.getRawType(this.type);
this.hashCode = this.type.hashCode();
}
/**
* Returns the type from super class's type parameter in {@link $Gson$Types#canonicalize
* canonical form}.
*/
static Type getSuperclassTypeParameter(Class<?> subclass) {
//对于parameterized type,会通过反射返回parameter的详细信息
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
ParameterizedType parameterized = (ParameterizedType) superclass;
//parameterized.getActualTypeArguments()[0] = java.util.List<java.lang.String>,
//canonicalize方法对Type进行包装,返回由Gson实现的Type类型,如$Gson$Types.ParameterizedTypeImpl
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
这样就可以将泛型具体的Type信息保存下来,在反序列化时可以准确还原object。这种通过匿名内部类来获取泛型信息的方式被称作Type-safe Heterogenerous,详细的说明可以参考TypeToken类的作者的Using TypeTokens to retrieve generic parameters.
总结
通过本次对Gson的部分代码分析,源码中令人惊艳的点主要有以下几个:
1、Gson的配置非常灵活,可以通过注册TypeAdapterFactory或注册TypeAdapter来实现用户的个性化配置。通过上下文来为client提供配置和功能延伸的入口,是编写一个工具类应当考虑的基本点;
2、在Gson上下文中使用缓存,对状态无关的配置进行全局缓存,提高效率。无论是编写工具类还是在日常的服务开发中,合理的缓存设计都是应对并发和提升效率的必要组件;
3、Gson中使用匿名类应对类型擦除的方式十分巧妙,在日后遇到同类问题的时候可以借鉴。