作者:陶征策 阿里国际站商家技术团队
Fastjson是阿里开发的Java语言编写的高性能JSON库,本文总结了Fastjson使用时的一些注意事项,并简单分析了Fastjson的底层工作原理,结合具体的验证代码以及跟Jackson的对比,希望能帮助大家充分理解Fastjson的使用。
一、为什么写这篇?
Fastjson是阿里开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject来分别实现序列化和反序列化操作,使用起来很方便。
最近在升级一个老系统的缓存架构,使用Fastjson将对象序列化后存入缓存,并在client端反序列化后使用,以减少对后端hsf的请求次数。在使用Fastjson的过程中踩了几个“坑”,颇费了一番周折,也趁此机会“深入”学习了下FastJson。这些“坑”本身并不是Fastjson的bug,更多的是一些平时容易被忽略的使用注意事项。因此总结了几个注意事项记录下来,方便后续学习参考。也希望能让更多同学理解如何去正确的使用Fastjson。
Jackson是另一个强大的JSON库,平时工作中使用也较为频繁,因此针对Fastjson的这些使用注意事项,增加了跟Jackson的横向比较。本文使用的Fastjson版本是1.2.68.noneautotype, 不支持AutoType。
二、知其然,怎么正确使用
注意点1:Null属性/值的反序列化
Fastjson在序列化时,默认是不会输出对象中的null属性和Map中value为null的key的, 如果想要输出null属性,可以在调用JSON.toJSONString函数时,加上SerializerFeature.WriteMapNullValue参数(开启Feature)。
一般情况下(比如在日志打印时),null属性/值是否被序列化输出其实影响不大。但是如果序列化的字符串需要再被反序列化成对象,这时候就需要格外注意了,尤其是涉及到Java Map/JSONObject等数据结构的序列化。下面通过代码展示下。
@Test
public void testFastjsonDeserializeNullFields() {
{
Map<String, String> mapWithNullValue = new HashMap<>();
mapWithNullValue.put("a", null);
mapWithNullValue.put("b", null);
String mapSerializeStr = JSON.toJSONString(mapWithNullValue);
Map<String, String> deserializedMap = JSON.parseObject(mapSerializeStr,
new TypeReference<Map<String, String>>() {});
if (mapWithNullValue.equals(deserializedMap)) {
System.out.println("Fastjson: mapWithNullValue is the same after deserialization");
} else {
System.out.println("Fastjson: mapWithNullValue is NOT the same after deserialization");
}
}
{
JSONObject jsonWithNullValue = new JSONObject();
jsonWithNullValue.put("a", null);
jsonWithNullValue.put("b", null);
String jsonSerializeStr = JSON.toJSONString(jsonWithNullValue);
JSONObject deserializedJson = JSON.parseObject(jsonSerializeStr,
new TypeReference<JSONObject>() {});
if (jsonWithNullValue.equals(deserializedJson)) {
System.out.println("Fastjson: jsonWithNullValue is the same after deserialization");
} else {
System.out.println("Fastjson: jsonWithNullValue is NOT the same after deserialization");
}
}
}
上述代码执行后的输出结果如下:
Fastjson: mapWithNullValue is NOT the same after deserialization
Fastjson: jsonWithNullValue is NOT the same after deserialization
可以看到,原对象和被反序列化的对象并不相同。原因是原对象(mapWithNullValue、jsonWithNullValue)的size是2,反序列化后的对象的size是0。
在一些业务场景中(比如对象序列化后缓存到tair中),需要保证原对象和反序列化的对象严格相同,则就要格外注意这个问题。在序列化时加上SerializerFeature.WriteMapNullValue参数,就能避免这个问题。
与Jackson的对比
相同的对象,如果我们换成使用Jackson来序列化和反序列化,则得到的结果是一致的。Jackson代码如下:
@Test
public void testJacksonDeserializeNullFields() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
{
Map<String, String> mapWithNullValue = new HashMap<>();
mapWithNullValue.put("a", null);
mapWithNullValue.put("b", null);
String mapSerializeStr = objectMapper.writeValueAsString(mapWithNullValue);
System.out.println("Jackson: mapSerializeStr: " + mapSerializeStr);
Map<String, String> deserializedMap = objectMapper.readValue(
mapSerializeStr,
new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() {});
if (mapWithNullValue.equals(deserializedMap)) {
System.out.println("Jackson: mapWithNullValue is the same after deserialization");
} else {
System.out.println("Jackson: mapWithNullValue is NOT the same after deserialization");
}
}
{
JSONObject jsonWithNullValue = new JSONObject();
jsonWithNullValue.put("a", null);
jsonWithNullValue.put("b", null);
String jsonSerializeStr = objectMapper.writeValueAsString(jsonWithNullValue);
System.out.println("Jackson: jsonSerializeStr: " + jsonSerializeStr);
JSONObject deserializedJson = objectMapper.readValue(
jsonSerializeStr, new com.fasterxml.jackson.core.type.TypeReference<JSONObject>() {});
if (jsonWithNullValue.equals(deserializedJson)) {
System.out.println("Jackson: jsonWithNullValue is the same after deserialization");
} else {
System.out.println("Jackson: jsonWithNullValue is NOT the same after deserialization");
}
}
}
结果输出如下,可以看到Jackson默认是会输出null值的。
Jackson: mapSerializeStr: {"a":null,"b":null}
Jackson: mapWithNullValue is the same after deserialization
Jackson: jsonSerializeStr: {"a":null,"b":null}
Jackson: jsonWithNullValue is the same after deserialization
使用建议
-
如果涉及到对象的反序列化,在调用JSON.toJSONString时,最好是加上SerializerFeature.WriteMapNullValue参数。
注意点2:Collection对象的反序列化
这是我在使用的时候,遇到的另一个“坑”,困扰了我很久,很难发现。在Java对象中,如果包含了Collection类型的成员变量,可能会遇到原对象和反序列化之后的对象不完全一样的问题。继续看下面的验证代码。
Class ObjectWithCollection是一个POJO类,定义了两个Collection类型的属性。
public class ObjectWithCollection {
private Collection<String> col1;
private Collection<Long> col2;
...setter...
...getter...
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (!(o instanceof ObjectWithCollection)) { return false; }
ObjectWithCollection that = (ObjectWithCollection) o;
return Objects.equals(col1, that.col1) &&
Objects.equals(col2, that.col2);
}
@Override
public int hashCode() {
return Objects.hash(col1, col2);
}
}
下面的代码尝试序列化objectWithCollection对象。objectWithCollection对象的col1属性被设置为一个ArrayList变量,col2属性被设置为一个HashSet变量。
@Test
public void testFastJsonDeserializeCollectionFields() {
ObjectWithCollection objectWithCollection = new ObjectWithCollection();
List<String> col1 = new ArrayList<>();
col1.add("str1");
Set<Long> col2 = new HashSet<>();
col2.add(22L);
objectWithCollection.setCol1(col1);
objectWithCollection.setCol2(col2);
String objectWithCollectionStr = JSON.toJSONString(objectWithCollection);
System.out.println("FastJson: objectWithCollectionStr: " + objectWithCollectionStr);
ObjectWithCollection deserializedObj = JSON.parseObject(objectWithCollectionStr,
ObjectWithCollection.class);
if (objectWithCollection.equals(deserializedObj)) {
System.out.println("FastJson: objectWithCollection is the same after deserialization");
} else {
System.out.println("FastJson: objectWithCollection is NOT the same after deserialization");
}
}
上述代码执行之后的结果如下:
FastJson: objectWithCollectionStr: {"col1":["str1"],"col2":[22]}
FastJson: objectWithCollection is NOT the same after deserialization
经过“深入研究”之后发现,在大部分情况下(在ASM开启的情况下,ASM功能默认是开启的),Fastjson使用HashSet类型来反序列化字符串类型的Json Array,使用ArrayList类型来反序列化其他类型(Long/Integer/Double/自定义Java类型等等)的Json Array。上面反序列化的对象deserializedObj的变量col1的实际类型是HashSet,这就导致了与原对象不相同了。
为什么会出现这个问题?是不是因为序列化时,Collection变量所对应的具体类型没有输出。如果将具体对象类型序列化输出(Fastjson支持此功能,但是默认关闭),Fastjson能否正确的反序列化呢?更新代码,在调用JSON.toJSONString方法时,加上SerializerFeature.WriteClassName参数。
@Test
public void testFastJsonDeserializeCollectionFields() {
ObjectWithCollection objectWithCollection = new ObjectWithCollection();
Collection<String> col1 = new ArrayList<>();
col1.add("str1");
Collection<Long> col2 = new HashSet<>();
col2.add(22L);
objectWithCollection.setCol1(col1);
objectWithCollection.setCol2(col2);
String objectWithCollectionStr = JSON.toJSONString(objectWithCollection,
SerializerFeature.WriteClassName);
System.out.println("FastJson: objectWithCollectionStr: " + objectWithCollectionStr);
ObjectWithCollection deserializedObj = JSON.parseObject(objectWithCollectionStr,
ObjectWithCollection.class);
if (objectWithCollection.equals(deserializedObj)) {
System.out.println("FastJson: objectWithCollection is the same after deserialization");
} else {
System.out.println("FastJson: objectWithCollection is NOT the same after deserialization");
}
}
再次运行试一下,结果输出如下。可以看到Fastjson正确输出了对象objectWithCollection的类型,正确输出了col2成员变量的类型("col2":Set[22L],注意Set关键字),但是未能输成员变量col1的具体类型,所以反序列化对象还是与原对象不相同。
FastJson: objectWithCollectionStr: {"@type":"com.test.utils.ObjectWithCollection","col1":["str1"],"col2":Set[22L]}
FastJson: objectWithCollection is NOT the same after deserialization
与Jackson的对比
同样的对象,我们试下用Jackson来序列化/反序列化。具体代码如下:
@Test
public void testJacksonDeserializeCollectionFields() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
ObjectWithCollection objectWithCollection = new ObjectWithCollection();
Collection<String> col1 = new ArrayList<>();
col1.add("str1");
Collection<Long> col2 = new HashSet<>();
col2.add(22L);
objectWithCollection.setCol1(col1);
objectWithCollection.setCol2(col2);
String objectWithCollectionStr = objectMapper.writeValueAsString(objectWithCollection);
System.out.println("Jackson: objectWithCollectionStr: " + objectWithCollectionStr);
ObjectWithCollection deserializedObj = objectMapper.readValue(objectWithCollectionStr,
ObjectWithCollection.class);
if (objectWithCollection.equals(deserializedObj)) {
System.out.println("Jackson: objectWithCollection is the same after deserialization");
} else {
System.out.println("Jackson: objectWithCollection is NOT the same after deserialization");
}
}
代码执行结果如下,发现反序列化的对象也是不相同的。
Jackson: objectWithCollectionStr: {"col1":["str1"],"col2":[22]}
Jackson: objectWithCollection is NOT the same after deserialization
再试一下在Jacskon序列化的时候输出对象类型。为了输出类型,我们需要设置objectMapper,增加一行代码如下。
objectMapper.enableDefaultTypingAsProperty(
ObjectMapper.DefaultTyping.NON_FINAL, "$type");
再次执行,很诧异,反序列化的对象竟然相同了。输出结果如下。能看到Jackson针对Collection类型变量,也是能输出一个具体的类型,这是跟FastJson的主要差异点。依赖于这个类型,Jackson能成功反序列化对象,保证和原对象一致。
Jackson: objectWithCollectionStr: {"$type":"com.test.utils.ObjectWithCollection","col1":["java.util.ArrayList",["str1"]],"col2":["java.util.HashSet",[22]]}
Jackson: objectWithCollection is the same after deserialization
使用建议
-
在定义需要被序列化的对象(POJO)时,避免使用Collection类型,可以使用List/Set等类型代替,这样可以避免很多的“麻烦”。
-
针对历史老代码/依赖的其他二方库的对象,如果已经使用了Collection类型,则推荐使用Jackson来序列化/反序列化,避免不必要的“坑”。
-
Fastjson针对Collection类型的成员变量的反序列化行为在不同条件下不太一致,这个确实有点难掌握,这个问题会在下面的章节中详细解释。
注意点3:缺少默认构造函数/setter,无法反序列化
在使用Fastjson的过程中,遇到的另一个“坑”是,针对一个对象,序列化是成功的,但是反序列化始终是不成功。在研究了下该对象所对应的代码后,发现了一些和其他对象的不同点:缺少默认的构造函数和变量所对应的setter。缺少这些函数的支撑,Fastjson无法成功反序列化,但是也不会抛出异常(有点奇怪,默默的就是不成功)。看下面的验证代码:
类ObjectWithOutSetter是一个没有默认构造函数(但是有其他带参数的构造函数)和setter的类,具体代码如下:
public class ObjectWithOutSetter {
private String var1;
private Long var2;
/*
public ObjectWithOutSetter() {
}
*/
public ObjectWithOutSetter(String v1, Long v2) {
var1 = v1;
var2 = v2;
}
public String getVar1() {
return var1;
}
public Long getVar2() {
return var2;
}
/*
public void setVar1(String var1) {
this.var1 = var1;
}
public void setVar2(Long var2) {
this.var2 = var2;
}
*/
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (!(o instanceof ObjectWithOutSetter)) { return false; }
ObjectWithOutSetter that = (ObjectWithOutSetter) o;
return Objects.equals(var1, that.var1) &&
Objects.equals(var2, that.var2);
}
@Override
public int hashCode() {
return Objects.hash(var1, var2);
}
}
@Test
public void testFastJsonDeserializeObjectWithoutDefaultConstructorAndSetter() {
ObjectWithOutSetter objectWithOutSetter = new ObjectWithOutSetter("StringValue1", 234L);
String objectWithOutSetterStr = JSON.toJSONString(objectWithOutSetter,
SerializerFeature.WriteMapNullValue);
System.out.println("FastJson: objectWithOutSetterStr: " + objectWithOutSetterStr);
ObjectWithOutSetter deserializedObj = JSON.parseObject(objectWithOutSetterStr,
ObjectWithOutSetter.class);
System.out.println("FastJson: deserializedObj Str: " +
JSON.toJSONString(deserializedObj, SerializerFeature.WriteMapNullValue));
if (objectWithOutSetter.equals(deserializedObj)) {
System.out.println("FastJson: objectWithOutSetter is the same after deserialization");
} else {
System.out.println("FastJson: objectWithOutSetter is NOT the same after deserialization");
}
}
上面的验证代码执行后,输出结果如下。可以看到反序列化对象deserializedObj的变量var1,var2都是null,反序列化不成功。
FastJson: objectWithOutSetterStr: {"var1":"StringValue1","var2":234}
FastJson: deserializedObj Str: {"var1":null,"var2":null}
FastJson: objectWithOutSetter is NOT the same after deserialization
如果将上面ObjectWithOutSetter类种的默认构造函数和setter代码的注释去掉,再重新执行上面的测试代码,就会发现反序列化的对象是一致的了。具体输出如下:
FastJson: objectWithOutSetterStr: {"var1":"StringValue1","var2":234}
FastJson: deserializedObj Str: {"var1":"StringValue1","var2":234}
FastJson: objectWithOutSetter is the same after deserialization
Fastjson的反序列化,需要依赖于类对象的默认构造函数和成员变量的setter,不然会失败。总结起来有如下的几种“可能”会失败的场景:
-
如果类对象缺少默认构造函数,反序列化肯定失败(不会抛异常,但是成员变量的值是null)。
-
如果类对象的private成员变量缺少setter,反序列化肯定失败,除非在反序列化调用JSON.parseObject时,加上参数Feature.SupportNonPublicField。特殊情况是,针对AtomicInteger/AtomicLong/AtomicBoolean/Map/Collection类型的成员变量,如果缺少对应的setter,也是能反序列化成功的。
-
同样的,如果一个类对象没有getter,则序列化也会失败的(不会抛异常,会输出空的“{}”字符串)。
针对类对象的public的成员变量,就算是没有setter,也能反序列化成功。
与Jackson的对比
同样的ObjectWithOutSetter对象(没有setter),换成用Jackson来序列化/反序列化,验证代码如下:
@Test
public void testJacksonDeserializeObjectWithoutDefaultConstructorAndSetter() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
ObjectWithOutSetter objectWithOutSetter = new ObjectWithOutSetter("StringValue1", 234L);
String objectWithOutSetterStr = objectMapper.writeValueAsString(objectWithOutSetter);
System.out.println("Jackson: objectWithOutSetterStr: " + objectWithOutSetterStr);
ObjectWithOutSetter deserializedObj = objectMapper.readValue(objectWithOutSetterStr,
ObjectWithOutSetter.class);
System.out.println("Jackson: deserializedObj Str: "
+ objectMapper.writeValueAsString(deserializedObj));
if (objectWithOutSetter.equals(deserializedObj)) {
System.out.println("Jackson: objectWithOutSetter is the same after deserialization");
} else {
System.out.println("Jackson: objectWithOutSetter is NOT the same after deserialization");
}
}
输出结果如下。可以看到,就算没有setter,Jackson也能正确的反序列化。
Jackson: objectWithOutSetterStr: {"var1":"StringValue1","var2":234}
Jackson: deserializedObj Str: {"var1":"StringValue1","var2":234}
Jackson: objectWithOutSetter is the same after deserialization
经过反复几次验证,针对Jackson的序列化/反序列化,总结如下:
-
如果类对象没有任何一个getter/@JsonProperty注解的变量,则序列化会失败。会抛出com.fasterxml.jackson.databind.exc.InvalidDefinitionException异常。
-
如果类对象缺少默认的构造函数,则反序列化会失败。会抛出com.fasterxml.jackson.databind.exc.InvalidDefinitionException异常。个人觉得这个设计更加合理,能帮助我们尽快发现问题。
-
如果类对象缺少对应的setter函数,反序列化还是会成功的。Jackson默认会使用Java Reflection来设置成员变量。这一点感觉Jackson还是很强大的。
Fastjson和Jackson,都需要依赖于对象的getter函数来完成序列化。
使用建议
-
针对需要被Fastjson序列化的对象,一定要定义好成员变量的getter函数。否则无法序列化。
-
针对需要使用Fastjson反序列化的对象,一定要定义默认构造函数和成员变量的setter函数,否则会失败。定义对象时,最好定义好默认构造函数、setter和getter,这样会避免很多麻烦。
-
针对历史老代码,如果缺少对应的setter函数,可以考虑是用Jackson来反序列化。
-
针对历史老代码,如果缺少默认构造函数和getter函数,无论使用Fastjson还是Jackson,反序列化的都会失败,只能改代码了。
注意点4:抽象类/接口的反序列化
这个注意点,跟上面的注意点3,其实是有点关联的。因为Fastjson在反序列化的时候需要依赖于默认构造函数和setter函数,如果无法“构造”出类对象,则反序列化肯定会失败的。比如在对象类型是接口(Interface)和抽象类(Abstract Class)时,是不能构造其对应的对象的,反序列化自然也不会成功。看下面的代码验证。
public class InterfaceObject implements TestInterface {
private String var1;
private Long data1;
...
}
public abstract class AbstractClass {
private String abStr1;
}
public class AbstractDemoObject extends AbstractClass {
private String var2;
private Long data2;
...
}
public class CompositeObject {
private TestInterface interfaceObject;
private AbstractClass abstractClass;
private Long data2;
...
}
@Test
public void testFastJsonDeserializeObjectWithInterface() {
CompositeObject compositeObject = new CompositeObject();
compositeObject.setData2(123L);
InterfaceObject interfaceObject = new InterfaceObject();
interfaceObject.setData1(456L);
interfaceObject.setVar1("StringValue1");
compositeObject.setInterfaceObject(interfaceObject);
AbstractDemoObject demoObject = new AbstractDemoObject();
demoObject.setVar2("StringValue2");
demoObject.setData2(789L);
demoObject.setAbStr1("abStr1");
compositeObject.setAbstractClass(demoObject);
String compositeObjectStr = JSON.toJSONString(compositeObject,
SerializerFeature.WriteMapNullValue);
System.out.println("FastJson: compositeObjectStr: " + compositeObjectStr);
CompositeObject deserializedObj = JSON.parseObject(compositeObjectStr,
CompositeObject.class);
System.out.println("FastJson: deserializedObj Str: " +
JSON.toJSONString(deserializedObj, SerializerFeature.WriteMapNullValue));
if (deserializedObj.getAbstractClass() == null) {
System.out.println("FastJson: deserializedObj.abstractClass is null");
}
if (deserializedObj.getInterfaceObject() == null) {
System.out.println("FastJson: deserializedObj.interfaceObject is null");
} else {
System.out.println("FastJson: deserializedObj.interfaceObject is not null. ClassName: "
+ deserializedObj.getInterfaceObject().getClass().getName());
}
}
上面代码的“关键之处”是CompositeObject类中,interfaceObject和abstractClass变量的类型,分别是接口和抽象类(都是基类类型)。验证代码执行后,结果输出如下,可以看到反序列化是失败的。
FastJson: compositeObjectStr: {"abstractClass":{"data2":789,"var2":"StringValue2"},"data2":123,"interfaceObject":{"data1":456,"var1":"StringValue1"}}
FastJson: deserializedObj Str: {"abstractClass":null,"data2":123,"interfaceObject":{}}
FastJson: deserializedObj.abstractClass is null
FastJson: deserializedObj.interfaceObject is not null. ClassName: com.sun.proxy.$Proxy15
从上面的输出中,我们还能发现,针对接口/抽象类变量,反序列化的行为还是有所差异的。反序列化对象deserializedObj中,抽象类变量abstractClass的值是null,而interface类型变量interfaceObject竟然不为null。判断是Fastjson可以根据interface,自动创建代理类(com.sun.proxy.*)来支持反序列化。
如果将CompositeObject类中的interfaceObject和abstractClass变量都改成子类类型,则序列化/反序列化可以正常工作。
也可以在序列化的时候,增加加上SerializerFeature.WriteClassName参数,使得序列化的字符串带上具体的类信息,但是在反序列化的时候会抛出“safeMode not support autoType”异常。Fastjson已经不支持基于autoType的自定义类的反序列化。
[ERROR] testFastJsonDeserializeObjectWithInterface(com.test.utils.FastjsonTest) Time elapsed: 0.673 s <<< ERROR!
com.alibaba.fastjson.JSONException: safeMode not support autoType : com.test.utils.AbstractDemoObject
at com.test.utils.FastjsonTest.testFastJsonDeserializeObjectWithInterface(FastjsonTest.java:343)
与Jackson的对比
相同的CompositeObject对象,如果通过Jackson来序列化,则同样会失败,直接抛出InvalidDefinitionException异常(如下)。Jackson也不支持接口/抽象类的反序列化。
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `com.test.utils.TestInterface` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
但是如果在序列化的时候加上具体的类信息,则Jackson可以正常工作。看下面的验证代码:
@Test
public void testJacksonDeserializeObjectWithInterface() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
// 增加这一行,输出具体类信息
objectMapper.enableDefaultTypingAsProperty(
ObjectMapper.DefaultTyping.NON_FINAL, "$type");
CompositeObject compositeObject = new CompositeObject();
compositeObject.setData2(123L);
InterfaceObject interfaceObject = new InterfaceObject();
interfaceObject.setData1(456L);
interfaceObject.setVar1("StringValue1");
compositeObject.setInterfaceObject(interfaceObject);
AbstractDemoObject demoObject = new AbstractDemoObject();
demoObject.setVar2("StringValue2");
demoObject.setData2(789L);
demoObject.setAbStr1("abStr1");
compositeObject.setAbstractClass(demoObject);
String compositeObjectStr = objectMapper.writeValueAsString(compositeObject);
System.out.println("Jackson: compositeObjectStr: " + compositeObjectStr);
CompositeObject deserializedObj = objectMapper.readValue(compositeObjectStr,
CompositeObject.class);
System.out.println("Jackson: deserializedObj Str: " +
objectMapper.writeValueAsString(deserializedObj));
if (deserializedObj.getAbstractClass() == null) {
System.out.println("Jackson: deserializedObj.abstractClass is null");
}
if (deserializedObj.getInterfaceObject() == null) {
System.out.println("Jackson: deserializedObj.interfaceObject is null");
} else {
System.out.println("Jackson: deserializedObj.interfaceObject is not null. ClassName: "
+ deserializedObj.getInterfaceObject().getClass().getName());
}
}
输出结果如下:
Jackson: compositeObjectStr: {"$type":"com.test.utils.CompositeObject","interfaceObject":{"$type":"com.test.utils.InterfaceObject","var1":"StringValue1","data1":456},"abstractClass":{"$type":"com.test.utils.AbstractDemoObject","abStr1":"abStr1","var2":"StringValue2","data2":789},"data2":123}
Jackson: deserializedObj Str: {"$type":"com.test.utils.CompositeObject","interfaceObject":{"$type":"com.test.utils.InterfaceObject","var1":"StringValue1","data1":456},"abstractClass":{"$type":"com.test.utils.AbstractDemoObject","abStr1":"abStr1","var2":"StringValue2","data2":789},"data2":123}
Jackson: deserializedObj.interfaceObject is not null. ClassName: com.test.utils.InterfaceObject
使用建议
-
定义需要被Fastjson序列化的对象时,不要使用接口(Interface)/抽象类(Abstract Class)类型定义成员变量,不然会导致反序列化失败。也不要使用基类(Base Class)类型定义成员变量,因为反序列化时会丢失子类的信息。
-
Fastjson的AutoType功能已经不建议使用了。默认只支持JDK的原生类,不再支持自定义Java类根据AutoType来反序列化。
-
针对历史老代码,已经使用了接口/抽象类/基类类型来定义成员变量,则可以考虑使用Jackson来序列化,但是必须输出对象的具体类型。
注意点5:“虚假”getter/setter的序列化/反序列化
通过上面的一些注意点分析,我们已经知道,Fastjson的序列化/反序列化是依赖对对象的默认构造函数、getter和setter函数的。但是在一些类对象中,经常能看到setXXX()/getXXX()等样式的函数,这些函数本身并不直接返回/设置一个对象,而是有一些内部处理逻辑(逻辑可复杂、可简单),只是刚好以set/get开头命名。这些函数往往会导致序列化/反序列化的失败,更严重的情况下甚至会造成系统安全漏洞(Fastjson的一些安全漏洞就是因为AutoType功能,同时配合特定的setter函数造成的,比如很常见的com.sun.rowset.JdbcRowSetImpl)。因为这些函数不直接对应到一个成员变量,我姑且称之为“虚假”getter/setter。
一个很典型的例子是阿里MetaQ的消息对象com.alibaba.rocketmq.common.message.MessageExt(这是个基类,实际会指向com.alibaba.rocketmq.common.message.MessageExtBatch)是无法被序列化的,会抛出BufferUnderflowException异常。应该很多人踩过这个坑,为此阿里的开发规约中,已经明确规定:日志打印时禁止直接用JSON工具将对象转换成String。
下面通过代码来验证下这个问题。下面的类中,定义了一个“虚假”的getter:getWired()。
public class ObjectWithWiredGetter {
private String var1;
private Long data1;
public String getVar1() {
return var1;
}
public void setVar1(String var1) {
this.var1 = var1;
}
public Long getData1() {
return data1;
}
public void setData1(Long data1) {
this.data1 = data1;
}
/**
* 注意这个函数
*
* @return
*/
public String getWired() {
return String.valueOf(1 / 0);
}
}
@Test
public void testFastJsonSerializeObjectWithWiredGetter() {
ObjectWithWiredGetter objectWithWiredGetter = new ObjectWithWiredGetter();
String objectWithWiredGetterStr = JSON.toJSONString(objectWithWiredGetter,
SerializerFeature.WriteMapNullValue);
System.out.println("FastJson: objectWithWiredGetter: " + objectWithWiredGetterStr);
}
上面的验证代码执行后,Fastjson在执行getWired()的逻辑时,直接抛出ArithmeticException异常,序列化失败。
[ERROR] testFastJsonSerializeObjectWithWiredGetter(com.test.utils.FastjsonTest) Time elapsed: 0.026 s <<< ERROR!
java.lang.ArithmeticException: / by zero
at com.test.utils.FastjsonTest.testFastJsonSerializeObjectWithWiredGetter(FastjsonTest.java:399)
上面的getWired()函数只是一个简单的demo。再延伸一下,针对有“复杂”处理逻辑的getter函数(比如在getter中调用hsf,写数据库等操作),Fastjson在序列化的时候,往往会有“意想不到”的结果发生,这通常不是我们所期望的。
怎么解决这个问题?在调用JSON.toJSONString的时候,加上SerializerFeature.IgnoreNonFieldGetter参数,忽略掉所有没有对应成员变量(Field)的getter函数,就能正常序列化。通过在getWired函数上增加@JSONField(serialize = false)注解,也能达到同样的效果。
@Test
public void testFastJsonSerializeObjectWithWiredGetter() {
ObjectWithWiredGetter objectWithWiredGetter = new ObjectWithWiredGetter();
objectWithWiredGetter.setVar1("StringValue1");
objectWithWiredGetter.setData1(100L);
String objectWithWiredGetterStr = JSON.toJSONString(objectWithWiredGetter,
SerializerFeature.WriteMapNullValue,
SerializerFeature.IgnoreNonFieldGetter);
System.out.println("FastJson: objectWithWiredGetter: " + objectWithWiredGetterStr);
}
加上参数后,再次执行验证代码,结果输出如下,序列化成功。
FastJson: objectWithWiredGetter: {"data1":100,"var1":"StringValue1"}
与Jackson的对比
Jackson的序列化也是依赖于getter的,同样的对象,如果采用Jackson来序列化,看下会发生什么。
@Test
public void testJacksonSerializeObjectWithWiredGetter() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
ObjectWithWiredGetter objectWithWiredGetter = new ObjectWithWiredGetter();
objectWithWiredGetter.setVar1("StringValue1");
objectWithWiredGetter.setData1(100L);
String objectWithWiredGetterStr = objectMapper.writeValueAsString(objectWithWiredGetter);
System.out.println("Jackson: objectWithWiredGetter: " + objectWithWiredGetterStr);
}
验证代码执行后,Jackson直接抛出JsonMappingException异常,具体如下。Jackson默认也不能很好的处理这种“复杂”的getter函数的序列化。
[ERROR] testJacksonSerializeObjectWithWiredGetter(com.test.utils.FastjsonTest) Time elapsed: 0.017 s <<< ERROR!
com.fasterxml.jackson.databind.JsonMappingException: / by zero (through reference chain: com.test.utils.ObjectWithWiredGetter["wired"])
at com.test.utils.FastjsonTest.testJacksonSerializeObjectWithWiredGetter(FastjsonTest.java:431)
Caused by: java.lang.ArithmeticException: / by zero
at com.test.utils.FastjsonTest.testJacksonSerializeObjectWithWiredGetter(FastjsonTest.java:431)
当然,Jackson也能通过配置化的方式,很方便的解决这个问题。直接在getWired函数上加上@JsonIgnore注解,就能成功序列化了。
/**
* 注意这个函数。注意@JsonIgnore注解
*
* @return
*/
@JsonIgnore
public String getWired() {
return String.valueOf(1 / 0);
}
使用建议
-
Fastjson和Jackson,默认都不能很好的处理“虚假”getter函数的序列化(虚假setter函数所对应的反序列化也是一样的)。需要被序列化/反序列化的对象,尽量不要定义有“虚假”的getter/setter,也不要让getter/setter包含“复杂”的处理逻辑。纯POJO会更好, Make it simple。
-
针对“虚假”getter/setter(没有与成员变量与之对应的setter/getter函数),一般不需要参与序列化/反序列化。可以通过SerializerFeature.IgnoreNonFieldGetter参数在序列化阶段将它们过滤掉。绝大多数场景下,加上SerializerFeature.IgnoreNonFieldGetter参数会更安全,能够避免一些“奇怪”的问题。也可以通过@JSONField注解的形式,来忽略掉它们。
注意点6:同引用对象的序列化
Fastjson有一个很强大的功能:循环引用检测(默认是开启的)。比如说有两个对象A和B (如下代码所示)。A包含了指向B对象的引用,B包含了指向A对象的引用,A/B两个对象就变成了循环引用了。这时候如果序列化,一般会遇到StackOverflowError的问题。正是因为Fastjson有了循环引用检测的能力,可以让对象A被“成功”的序列化。我们通过如下的代码来验证下:
public class DemoA {
private DemoB b;
}
public class DemoB {
private DemoA a;
}
@Test
public void testFastJsonSerializeCircularObject() {
DemoA A = new DemoA();
DemoB B = new DemoB();
A.setB(B);
B.setA(A);
String demoAStr = JSON.toJSONString(A,
SerializerFeature.WriteMapNullValue);
System.out.println("FastJson: demoA serialization str: " + demoAStr);
}
执行后的输出如下所示。对象A被“成功”序列化了,序列化成了一种“奇怪”的字符串展示形式。这背后正是循环引用检测的作用。如果检测到有循环引用,就通过“$ref”来表示引用的对象。
FastJson: demoA serialization str: {"b":{"a":{"$ref":".."}}}
Fastjson一共支持4种对象引用,分别如下。
循环引用检测的能力确实很强大,但有时候“能力”过于强大,就会“殃及无辜”,造成一些误杀。比如对象明明没有“循环引用”,却还是按照引用模式进行序列化了。看如下的代码。
public class RefObject {
private String var1;
private Long data1;
...setter/getter....
}
public class SameRefObjectDemo {
private List<RefObject> refObjectList;
private Map<String, RefObject> refObjectMap;
...setter/getter....
}
@Test
public void testFastJsonSerializeSameReferenceObject() {
RefObject refObject = new RefObject();
refObject.setVar1("Value1");
refObject.setData1(9875L);
SameRefObjectDemo sameRefObjectDemo = new SameRefObjectDemo();
List<RefObject> refObjects = new ArrayList<>();
refObjects.add(refObject);
refObjects.add(refObject);
sameRefObjectDemo.setRefObjectList(refObjects);
Map<String, RefObject> refObjectMap = new HashMap<>();
refObjectMap.put("key1", refObject);
refObjectMap.put("key2", refObject);
sameRefObjectDemo.setRefObjectMap(refObjectMap);
String sameRefObjectDemoStr = JSON.toJSONString(sameRefObjectDemo,
SerializerFeature.WriteMapNullValue);
System.out.println("FastJson: sameRefObjectDemoStr: " + sameRefObjectDemoStr);
SameRefObjectDemo deserializedObj = JSON.parseObject(sameRefObjectDemoStr,
SameRefObjectDemo.class);
System.out.println("FastJson: deserializedObj Str: " +
JSON.toJSONString(deserializedObj, SerializerFeature.WriteMapNullValue));
if (sameRefObjectDemo.equals(deserializedObj)) {
System.out.println("FastJson: sameRefObjectDemo is the same after deserialization");
} else {
System.out.println("FastJson: sameRefObjectDemo is NOT the same after deserialization");
}
}
输出结果如下。可以看到sameRefObjectDemo对象确实是被序列化成引用字符串了。
FastJson: sameRefObjectDemoStr: {"refObjectList":[{"data1":9875,"var1":"Value1"},{"$ref":"$.refObjectList[0]"}],"refObjectMap":{"key1":{"$ref":"$.refObjectList[0]"},"key2":{"$ref":"$.refObjectList[0]"}}}
FastJson: deserializedObj Str: {"refObjectList":[{"data1":9875,"var1":"Value1"},{"$ref":"$.refObjectList[0]"}],"refObjectMap":{"key1":{"$ref":"$.refObjectList[0]"},"key2":{"$ref":"$.refObjectList[0]"}}}
FastJson: sameRefObjectDemo is the same after deserialization
sameRefObjectDemo对象并不包含循环引用,只是重复引用了同一个对象“refObject”(我称之为同引用对象)。这种形式的Java对象,在日常业务逻辑中还是挺常见的。如果是按照“引用”模式来序列化,是会造成一些影响的,比如前后端交互,前端无法解析这种“奇怪”的Json字符串。在比如异构系统之间的交互,使用了不同的Json框架,会造成彼此之间的通信失败。
为什么同引用对象也要按照“循环引用”的模式来序列化,我能想到的就是为了减少序列化的结果输出长度,降低网络传输开销,有利有弊。
如果在调用JSON.toJSONString函数是加上SerializerFeature.DisableCircularReferenceDetect参数,就能禁用“循环引用”检测功能,得到正常的输出如下:
FastJson: sameRefObjectDemoStr: {"refObjectList":[{"data1":9875,"var1":"Value1"},{"data1":9875,"var1":"Value1"}],"refObjectMap":{"key1":{"data1":9875,"var1":"Value1"},"key2":{"data1":9875,"var1":"Value1"}}}
与Jackson的对比
Jackson默认是不支持“循环引用”的对象序列化的,会抛出StackOverflowError错误(具体如下):
com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)
但是它提供了很多的注解,可以解决这个问题。这篇文章《Jackson – Bidirectional Relationships》详细解释了多种方案。这里我们验证一种方法: 使用@JsonManagedReference和@JsonBackReference注解。具体代码如下:
《Jackson – Bidirectional Relationships》参考链接:
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
public class DemoA {
@JsonManagedReference
private DemoB b;
...
}
public class DemoB {
@JsonBackReference
private DemoA a;
private String str1;
...
}
@Test
public void testJacksonSerializeCircularObject() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
DemoA A = new DemoA();
DemoB B = new DemoB();
A.setB(B);
B.setA(A);
B.setStr1("StringValue1");
String demoAStr = objectMapper.writeValueAsString(A);
System.out.println("Jackson: demoA serialization str: " + demoAStr);
DemoA deserializedObj = objectMapper.readValue(demoAStr, DemoA.class);
if (deserializedObj.getB() != null) {
System.out.println("Jackson: demoB object is not null. "
+ "B.str1: " + deserializedObj.getB().getStr1()
+ ", B.a: "
+ ((deserializedObj.getB().getA() == null) ? "(null)" : "(not null)"));
}
}
输出结果如下。可以看到demoA对象能被正确的序列化,且反序列化的对象deserializedObj中,变量b是有正确值的。
Jackson: demoA serialization str: {"b":{"str1":"StringValue1"}}
Jackson: demoB object is not null. B.str1: StringValue1, B.a: (not null)
使用建议
-
在跟前端系统通过HTTP接口交互时,如果使用了Fastjson序列化,尽量设置SerializerFeature.DisableCircularReferenceDetect参数,禁用掉“循环引用”的检测能力。
-
“循环引用”通常意味着不良的设计,需要被重构掉。使用SerializerFeature.DisableCircularReferenceDetect参数,禁用掉“循环引用”的检测能力,能让问题尽早暴露,尽早被发现。
-
针对确实需要处理“循环引用”的场景,Fastjson使用起来会更方便。
三、知其所以然,打破砂锅问到底
上面的很大篇幅中,列举了使用Fastjson的一些注意事项,介绍了怎么去更合理的使用它,但是我们还处在“知其然”的阶段。上面的注意事项中也遗留了一些问题,还没有解答。为了解答这些问题,同时也为了更加深刻的理解Fastjson的底层工作原理,我们还需要做到“知其所以然”,这样使用起来才能得心应手,灵活应变。
3.1 对象是怎么被序列化的?
以我们常用的JSON.toJSONString来分析Java对象的序列化,这里涉及到Fastjson的几个关键对象, 他们之间的关系如下所示:
上面的类图中,最核心的类是SerializeConfig和JavaBeanSerializer。SerializeConfig的主要职责有两点:
-
维护了一个IdentityHashMap,里面存储了不同的Java类及其对应的Serializer之间的关系。每次调用JSON.toJSONString序列化时,都会从中查找对应的Serializer。
-
如果找不到一个类对象的Serializer(一般是自定义Java对象),则会重新创建一个JavaBeanSerializer,并放入IdentityHashMap中,以供下次使用。
JavaBeanSerializer主要是用来序列化自定义Java对象的。Fastjson在从SerializeConfig找到对应类的Serializer后,直接调用Serializer的write接口,即可完成序列化。一个自定义类对象,它的每一个成员变量(field)对象都会有它对应的FieldSerializer。在类对象的序列化过程中会依次调用FieldSerializer,下图展示了一个简单的Java POJO对象在序列化时会需要用到的Serializer。
3.1.1 有多少种Serializer?
一个Java自定义对象的序列化还是很复杂的,因为涉及到很多的其他Java自定义对象和Java的primitive类型。针对Java的primitive类型,Fastjson基本都定义了对应的Serializer,可以很好的支持它们的序列化工作。所有实现了com.alibaba.fastjson.serializer.ObjectSerializer接口的类,都是Fastjson默认已经定义好的Serializer,粗略看了下,大概有44个。
3.1.2 JavaBeanSerializer是怎么创建的?
JavaBeanSerializer是Fastjson序列化的核心类。在默认情况下,Fastjson会给每一个需要被序列化的自定义Java类创建一个JavaBeanSerializer对象或者通过ASM(字节码修改)技术动态生成一个JavaBeanSerializer的子类对象,由他们来完成核心的序列化工作。JavaBeanSerializer子类的命名规范是:ASMSerializer_<Random Number>_<Original Class Name>, 例如:ASMSerializer_4_ObjectWithWiredGetter。
为什么需要通过ASM技术创建JavaBeanSerializer的子类对象?主要还是为了效率和性能,这也是Fastjson为什么快的原因之一。通过ASM技术创建的JavaBeanSerializer的子类对象,是高度定制化的,是跟需要被序列化的自定义Java类紧密绑定的,所以可以取得最佳的性能。
是否通过ASM技术创建JavaBeanSerializer的子类对象,主要取决于“类品”。人有人品,类当然也有“类品”。人品决定了我们做事结果的好坏,“类品”决定了在序列化时是否能使用ASM功能。“类品”简单理解就是类的一个综合属性,会根据类的基类特点、类的名称,成员变量个数、getter/setter、是否是interface、是否有使用JSONField注解等等信息来综合决定。下面的代码片段展示了JavaBeanSerializer的创建过程,asm变量默认是true的。Fastjson会做一系列的判断,来决定asm变量的值,进而决定是否创建ASMSerializer。
下图展示了JavaBeanSerializer及其依赖的SerializeBeanInfo、FieldSerializer之间的关系。
JavaBeanSerializer的创建,依赖于SerializeBeanInfo。SerializeBeanInfo针对一个Java类,只会被创建一次(调用com.alibaba.fastjson.util.TypeUtils#buildBeanInfo方法来创建,跟随JavaBeanSerializer一起创建),主要包含了以下的信息:
-
beanType:也就是Java类对象的真实类型。
-
jsonType:如果一个Java类对象被@JSONType注解修饰,则有值,否则为null。
-
typeName:依赖于上面的jsonType。如果jsonType中有指定序列化输出的类型,则有值。
-
features:依赖于上面的jsonType。如果jsonType中有指定序列化输出的SerializerFeature,则有值。
-
fields:Java类对象中需要被序列化的get方法/field信息等等,都包含在其中,这个字段是比较关键的。
SerializeBeanInfo对象决定了一个Java对象序列化的输出。JavaBeanSerializer会根据SerializeBeanInfo对象中的fields字段,创建对应的成员变量的FieldSerializer。
FieldSerializer中的RuntimeSerializerInfo在创建的时候为null,在真正使用的时候才被初始化,指向具体的Serializer(Lazy initialization模式)。
3.1.3 怎么自定义Serializer?
在搞清楚了Serializer的查找方式和工作原来之后,我们就能“照葫芦画瓢”,写自己的Serializer了。下面通过DummyEnum类,来展示下怎么写一个Serializer。
先创建DummyEnum类,如下:
public enum DummyEnum {
/**
* 停用
*/
DISABLED(0, "Disabled"),
/**
* 启用
*/
ENABLED(1, "Enabled"),
/**
* 未知
*/
UNKNOWN(2, "Don't Known");
private int code;
private String desc;
DummyEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public static DummyEnum from(int value) {
for (DummyEnum dummyEnum : values()) {
if (value == dummyEnum.getCode()) {
return dummyEnum;
}
}
return null;
}
...getter
...setter
}
再创建DummyEnumSerializer,Serializer需要实现ObjectSerializer接口,代码如下:.
public class DummyEnumSerializer implements ObjectSerializer {
@Override
public void write(JSONSerializer serializer, Object object,
Object fieldName, Type fieldType, int features)
throws IOException {
if (object == null) {
serializer.out.writeNull();
} else {
DummyEnum myEnum = (DummyEnum) object;
// 序列化时调用getCode方法
serializer.out.writeInt(myEnum.getCode());
}
}
}
最后,只需要创建DummyEnumSerializer对象,注册到Global SerializeConfig中就好了。
@Test
public void testFastJsonSerializeDummyEnum() {
try {
DummyEnum dummyEnum = DummyEnum.UNKNOWN;
// 把DummyEnumSerializer插入到全局的SerializeConfig中
SerializeConfig globalConfig = SerializeConfig.getGlobalInstance();
globalConfig.put(DummyEnum.class, new DummyEnumSerializer());
String dummyEnumStr = JSON.toJSONString(dummyEnum,
SerializerFeature.WriteMapNullValue);
System.out.println("FastJson: dummyEnumStr: " + dummyEnumStr);
} catch (Exception e) {
e.printStackTrace();
}
}
Enum类默认的序列化是输出枚举变量的名字(name)。上面的验证代码执行后,输出数字2,表示新的Serializer成功执行了。
FastJson: dummyEnumStr: 2
3.2 字符串是怎么被反序列化的?
Fastjson支持反序列化的核心函数有三个,他们的主要功能和区别如下:
1)JSON.parse(String): 直接从字符串序列化,返回一个Java对象。
-
如果字符串是Collection类型(Set/List等)Java对象序列化生成的(类似“[...]”),则此函数会返回JSONArray对象。
-
如果字符串是从自定义对象序列化生成的(类似“{...}”),则此函数会返回JSONObject对象。
-
如果字符串中指定了对象类型(比如Set/TreeSet等),也会直接返回对应的Set/TreeSet Java对象。
2)JSON.parseObject(String):直接从字符串序列化,返回一个JSONObject对象。
-
如果字符串是Collection类型(Set/List等)Java对象序列化生成的, 则此函数会抛出异常:“com.alibaba.fastjson.JSONException: can not cast to JSONObject.”。
3)JSON.parseObject(String,Clazz): 根据Java Clazz类型,将字符串反序列化成一个自定义的Java对象。也可以支持List/Set/Map等Java原生对象的生成。
因为Fastjson默认已经禁用了AutoType(在1.2.68版本中),在不考虑AutoType功能影响的情况下,这三个接口之间的对比如下:
3.2.1 parseObject(String)的工作原理
这三个接口中,parse(String)和parseObject(String)接口的功能实现非常的类似。后者会调用前者,并将后者的结果转成对应的JSONObject。这里重点分析下parseObject(String)接口的工作原理。
上图展示了parseObject(String)接口主要的代码,其主要分成两步:
-
调用parse(String)接口,将字符串反序列化成对象
-
调用JSON.toJSON接口,将步骤1中的对象转成JSONObject
parse(String)接口的代码很少,如以下截图所示,它的核心是DefaultJSONParser。
DefaultJSONParser是反序列化的主要驱动类,它包含两个核心的对象:
-
ParseConfig:反序列化的核心配置类,其中有一个IdentityHashMap,维护了一些列的对象类型和deserializers的对应关系。当没有对应的deserializer时,会创建新的,并放入IdentityHashMap中。默认情况下,使用的是全局的配置(com.alibaba.fastjson.parser.ParserConfig#global)。
-
JSONLexter:是一个基类,真实指向一个JSONScanner对象。是JSON字符串解析的核心对象。根据字符串的解析结果,逐个字符往前移动,直到结尾。
DefaultJSONParser会判断字符串是列表型(以"["开头的)还是对象型(以"{"开头的)
-
如果是列表型,则解析字符串,反序列化返回一个JSONArray。
-
如果是对象型,则反序列化返回一个JSONObject。在反序列化的过程中,基本思路是在一个for循环中,调用JSONLexter,先解析出JSON key,接着再解析出JSON value。把key/value存入JSONObject的内部map中。
parse(String)在解析的过程中,因为不涉及到具体的类对象的构建,所以一般不涉及到deserializer的调用。
3.2.2 parseObject(String, Clazz)的工作原理
JavaBeanDeserializer是怎么创建的?
相比于上面的parse(String)接口,这个接口的工作原理是要更复杂些,因为涉及到Clazz所对应的JavaBeanDeserializer的创建,这个主要是在ParseConfig类中完成的。
与JavaBeanSerializer的创建过程类似,Fastjson会给每一个需要被序列化的自定义Java类创建一个JavaBeanDeserializer对象或者通过ASM技术动态生成一个JavaBeanDeserializer的子类对象(也是为了效率和性能),由他们来完成核心的反序列化工作。JavaBeanDeserializer子类的命名规范是:FastjsonASMDeserializer_<Random Number>_<Original Class Name>, 例如:FastjsonASMDeserializer_1_ObjectWithCollection。
是否通过ASM技术创建JavaBeanDeserializer的子类对象,同样主要取决于“类品”。下面的代码清晰展示了JavaBeanDeserializer的创建过程。asmEnable变量默认是true,Fastjson会根据“类品”做一系列判断,决定是否使用ASM功能。
下图展示了JavaBeanDeserializer及其依赖的JavaBeanInfo、FieldDeserializer之间的关系。
创建JavaBeanDeserializer的主要步骤可以概括为如下:
1)根据Clazz类类型,在ParseConfig中的IdentityHashMap查找对应的Deserializer,如果有直接返回。
2)如果没有,则继续下面的创建步骤:
-
调用com.alibaba.fastjson.util.JavaBeanInfo#build(...) 创建JavaBeanInfo。这一步极为关键,正是在这一步中会提取Java类的“元信息”:
-
根据反射,找到Clazz类型所对应的默认构造函数,setter函数,getter函数等信息。
-
从setter/getter函数出发,找到对应成员变量(field),以及判断setter/getter/field上是否有被JSONField注解修饰。
-
针对每一个getter/setter,会创建一个FieldInfo(会去重的),并构建FieldInfo的数组fields。FieldInfo包含了每个成员变量的name(变量名称),field(Java反射字段信息),method(访问变量的Java函数反射信息),fieldClass(变量的类型),fieldAnnotation(成员变量是否被JSONField修饰,及其信息)等诸多“元信息”。
-
-
如果ASM功能开启,则通过ASMDeserializerFactory创建JavaBeanDeserializer。在JavaBeanDeserializer构造函数中,根据JavaBeanInfo中的fields列表,创建fieldDeserializers数组,用于反序列化成员变量。
-
FieldDeserializer中的ObjectDeserializer变量,默认是null,直到第一次使用的时候才被初始化。
3)将创建好的Deserializer放入ParseConfig中,以供下次使用。
反序列化Deserializer的执行
一旦根据Clazz信息,找到/创建了对应的Deserializer,那就很简单了,直接调用Deserializer的deserialze(...)完成序列化。Fastjson已经内置了30多种的Deserializer,常用的Java对象,基本都已经默认支持了。
FastjsonASMDeserializer的执行:
通过ASM字节码技术动态创建的FastjsonASMDeserializer,因为和具体的Java自定义类(以上面的ObjectWithCollection类举例)直接相关,执行流程也是“千类千面”,总结起来大致如下:
-
直接new一个ObjectWithCollection对象(假设是对象T)。
-
逐个根据已经生成好的JSON key(这里是指"col1:", "col2:"),匹配JSON字符串,尽力去反序列化每个JSON key所对应的Java对象,并设置到对象T上。下图展示的是反编译的代码。
-
调用JavaBeanDeserializer的parseRest(...)接口,继续反序列化剩余的JSON字符串直到字符串结束,设置并返回对象T。
JavaBeanDeserializer的执行:
JavaBeanDeserializer的执行,因为涉及到要对每一个成员变量执行FieldDeserializer,会相对复杂一些。主要流程概括起来如下(在代码com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze中):
-
通过Java反射,调用类对象的默认构造函数创建类对象(比如对象T)。如果没有默认构造函数,则先创建一个Map对象(M),保存创建的成员变量对象(field value)。
-
在for循环中,遍历获取每一个FieldDeserializer(FieldDeserializer是在一个数组中,经过排序的),及其所关联的fieldInfo。从fieldInfo获取每一个成员变量的类型(fieldClass)和对应的JSON key name(比如是"ABC":)。通过JSONLexer判断当前的JSON字符串(比如字符串是"ABC":123)中的key是否等于JSON key name。如果相等,则直接解析出成员变量对象(field value),并设置到对象T上或者存储到M中。
-
当所有的FieldDeserializer都遍历完之后,如果JSON字符串还没有解析完,则驱动JSONLexer解析出当前字符串(比如"XYZ":123)对应的JSON key("XYZ":)。通过JSON key反查到fieldDeserializer,通过fieldDeserializer继续解析当前的字符串。
-
继续for循环,直到JSON字符串解析结束或者抛出异常。
下面通过FastJsonDemoObject类,展示了该类对象及其成员变量反序列化时所分别调用的Deserializers。
3.2.3 怎么自定义Deserializer?
从上面的分析可知,FastJson的反序列化deserializer,都是实现了ObjectDeserializer接口的,并且是从PaserConfig中获取的。在掌握这些原理后,定义自己的deserializer就变的“信手拈来”了。继续以上文的DummyEnum为例子,我们定义的反序列化DummyEnumDeserializer如下:
public class DummyEnumDeserializer implements ObjectDeserializer {
/**
* 从int值反序列化Dummy Enum
*
* @param parser
* @param type
* @param fieldName
* @param <T>
* @return
*/
@Override
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
final JSONLexer lexer = parser.lexer;
int intValue = lexer.intValue();
lexer.nextToken(JSONToken.COMMA);
DummyEnum dummyEnum = DummyEnum.from(intValue);
System.out.println("DummyEnumDeserializer executed");
if (dummyEnum != null) {
return (T) dummyEnum;
} else {
return null;
}
}
/**
* 获取当前json字符串位置的token标志值
*
* @return
*/
@Override
public int getFastMatchToken() {
return JSONToken.LITERAL_INT;
}
}
最后,我们创建DummyEnumDeserializer对象,插入到ParserConfig中就好了。
@Test
public void testFastJsonSerializeDummyEnum() {
try {
DummyEnum dummyEnum = DummyEnum.UNKNOWN;
// 把DummyEnumSerializer插入到全局的SerializeConfig中
SerializeConfig globalConfig = SerializeConfig.getGlobalInstance();
globalConfig.put(DummyEnum.class, new DummyEnumSerializer());
// 把DummyEnumDeserializer插入到全局的ParserConfig中
ParserConfig parserConfig = ParserConfig.getGlobalInstance();
parserConfig.putDeserializer(DummyEnum.class, new DummyEnumDeserializer());
String dummyEnumStr = JSON.toJSONString(dummyEnum,
SerializerFeature.WriteMapNullValue);
System.out.println("FastJson: dummyEnumStr: " + dummyEnumStr);
DummyEnum deserializedEnum = JSON.parseObject(dummyEnumStr, DummyEnum.class);
System.out.println("FastJson: deserializedEnum desc: " + deserializedEnum.getDesc());
} catch (Exception e) {
e.printStackTrace();
}
}
上面的测试案例更新后,执行结果输出如下。能看到DummyEnumDeserializer被成功调用了,能成功反序列化DummyEnum。
FastJson: dummyEnumStr: 2
DummyEnumDeserializer executed
FastJson: deserializedEnum desc: Don't Know
除了直接修改全局的SerializeConfig/ParserConfig来注册Serializer/Deserializer的方式,FastJson还提供了通过JSONType注解的方式来指定Serializer/Deserializer,使用起来更加方便。如下图所示。
如果类上有指定JSONType的注解,则在创建Deserializer的时候,优先通过注解上指定的类来创建。
3.2.4 为什么反序列化的时候会调用getter函数?
前面我们提到,parseObject(String, Clazz)接口的反序列化,在特定情况下会调用类对象的getter函数。反序列化的时候,因为要设置类对象的成员变量,所以会调用setter函数,这是可以理解的。但是为什么也会调用getter函数,始终没想明白。后来在代码中找到了一点线索,在每次调用FieldDeserializer反序列化完一个成员变量时,会调用setValue函数将成员变量的value设置到对象上。在满足以下两个条件时,会调用该成员变量所对应的getter函数:
-
该成员变量没有对应的setter函数(如果有setter就会直接调用setter了,不会走这招“曲线救国”)。
-
该成员变量的类型是AtomicInteger/AtomicLong/AtomicBoolean/Map/Collection(我理解这些类型的对象都是mutable的,有通用的接口来改变它们的值)。
3.2.5 为什么Collection类型的反序列化结果取决于“类品”?
在上面的注意点2中提到,Fastjson针对Collection类型的成员变量的反序列化行为在不同条件下不太一致。Collection类型的成员变量在反序列化后,具体是指向ArrayList还是HashSet,这个要看“类品”了。Fastjson在反序列化时,会根据具体类的“类品”来判断是否开启ASM功能。如果判断能开启ASM功能,则会使用ASM字节码操作技术生成一个特定的ASM Deserializer对象,否则使用默认的JavaBeanDeserializer对象。不同Deserializer的使用,造成了反序列化结果的不同。
使用ASM Deserializer
在ASM功能开启的情况下,Fastjson会动态创建特定的ASM Deserializer。此处以FastjsonASMDeserializer_1_ObjectWithCollection举例(它也是继承于JavaBeanDeserializer的),代码如下。
上述代码在解析ObjectWithCollection类的col1成员变量时(Collection<String>类型),默认会调用JSONLexer的scanFieldStringArray(...)函数。scanFieldStringArray函数会调用newCollectionByType(...)来创建具体的Collection类型。
newCollectionByType默认第一个返回的HashSet类型的对象。这也就解释了上面的注意点2中的问题。
在ASM Deserializer中,其他非字符串类型的Collection成员变量,默认是使用ArrayList来反序列化的,除非明确指定了Json Array的类型(比如指定了类型是Set,则Fastjson的token值是21)。
使用默认JavaBeanDeserializer
如果是使用默认的JavaBeanDeserializer,针对Json Array,一般是会调用CollectionCodec Deserializer来反序列化,createCollection函数默认返回ArrayList类型。这是符合我们认知的,这里不再赘述。
四、写在最后
整体来说,Fastjson的功能还是很强大的,使用也比较简单。可能也正是因为Fastjson的容易使用,让我们忽略了对其内部工作原理的研究,未能关注到在特定使用场景下可能造成的“问题”。本文第一部分花了很大的篇幅去总结Fastjson的一些使用注意事项,并结合具体的验证代码以及跟Jackson的对比,希望能帮助大家充分理解Fastjson的使用,在具体场景下调整使用方法,灵活运用。第二部分简单分析了一下Fastjson的底层工作原理,关键点在于掌握Serializer/Deserializer的创建和执行。整体的代码结构还是比较统一和清晰的,设计巧妙,支持的功能很丰富。希望能帮助大家“深入”了解Fastjson,掌握更多的设计方法。
上面的学习和分析,也只是涵盖了Fastjson的少部分功能,很多的功能和细节还没有涉及到。以上这些都是根据自己的研究和理解所写,一家之言,如有不实之处,欢迎指正和交流,一起学习。