cover_image

Fastjson踩“坑”记录和“深度”学习

陶征策 阿里技术
2023年02月10日 00:30

图片


这是阿里技术2023年的第7篇文章

( 本文阅读时间:15分钟 )


Fastjson是阿里开发的Java语言编写的高性能JSON库,本文总结了Fastjson使用时的一些注意事项,并简单分析了Fastjson的底层工作原理,结合具体的验证代码以及跟Jackson的对比,希望能帮助大家充分理解Fastjson的使用。



01



为什么写这篇?

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。



02



知其然,怎么正确使用

注意点1:Null属性/值的反序列化

Fastjson在序列化时,默认是不会输出对象中的null属性和Map中value为null的key的, 如果想要输出null属性,可以在调用JSON.toJSONString函数时,加上SerializerFeature.WriteMapNullValue参数(开启Feature)。


一般情况下(比如在日志打印时),null属性/值是否被序列化输出其实影响不大。但是如果序列化的字符串需要再被反序列化成对象,这时候就需要格外注意了,尤其是涉及到Java Map/JSONObject等数据结构的序列化。下面通过代码展示下。


@Testpublic 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 deserializationFastjson: jsonWithNullValue is NOT the same after deserialization


可以看到,原对象和被反序列化的对象并不相同。原因是原对象(mapWithNullValue、jsonWithNullValue)的size是2,反序列化后的对象的size是0。


在一些业务场景中(比如对象序列化后缓存到tair中),需要保证原对象和反序列化的对象严格相同,则就要格外注意这个问题。在序列化时加上SerializerFeature.WriteMapNullValue参数,就能避免这个问题。

✪ 与Jackson的对比

相同的对象,如果我们换成使用Jackson来序列化和反序列化,则得到的结果是一致的。Jackson代码如下:


@Testpublic 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变量。


@Testpublic 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参数。


@Testpublic 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来序列化/反序列化。具体代码如下:


@Testpublic 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); }}
@Testpublic 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来序列化/反序列化,验证代码如下:


@Testpublic 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; ...}@Testpublic 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 nullFastJson: 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可以正常工作。看下面的验证代码:


@Testpublic 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); }}
@Testpublic 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)注解,也能达到同样的效果。


@Testpublic 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来序列化,看下会发生什么。


@Testpublic 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 */@JsonIgnorepublic 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;}
@Testpublic 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....}
@Testpublic 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; ...}
@Testpublic 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使用起来会更方便。



03



知其所以然,打破砂锅问到底

上面的很大篇幅中,列举了使用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中就好了。


@Testpublic 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支持反序列化的核心函数有三个,他们的主要功能和区别如下:


  • JSON.parse(String): 直接从字符串序列化,返回一个Java对象。


    1. 如果字符串是Collection类型(Set/List等)Java对象序列化生成的(类似“[...]”),则此函数会返回JSONArray对象


    2. 如果字符串是从自定义对象序列化生成的(类似“{...}”),则此函数会返回JSONObject对象。


    3. 如果字符串中指定了对象类型(比如Set/TreeSet等),也会直接返回对应的Set/TreeSet Java对象。


  • JSON.parseObject(String):直接从字符串序列化,返回一个JSONObject对象。


    1. 如果字符串是Collection类型(Set/List等)Java对象序列化生成的, 此函数会抛出异常:“com.alibaba.fastjson.JSONException: can not cast to JSONObject.”。


  • JSON.parseObject(String,Clazz): 根据Java Clazz类型,将字符串反序列化成一个自定义的Java对象。也可以支持List/Set/Map等Java原生对象的生成。


因为Fastjson默认已经禁用了AutoType(在1.2.68版本中),在不考虑AutoType功能影响的情况下,这三个接口之间的对比如下:


左滑查看

接口

返回

JSONArray

返回

JSONObject

返回

自定义对象

调用setter

调用getter

parse(String)

Y

Y

Y(特定情况)

N

N

parseObject(String)

N

Y

N

N

N

parseObject(String, Clazz)

Y

Y

Y

Y

Y(特定数据类型)

✪ 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类的“元信息”:


    1. 根据反射,找到Clazz类型所对应的默认构造函数,setter函数,getter函数等信息。


    2. 从setter/getter函数出发,找到对应成员变量(field),以及判断setter/getter/field上是否有被JSONField注解修饰。


    3. 针对每一个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中就好了。


@Testpublic 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: 2DummyEnumDeserializer executedFastJson: 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类型。这是符合我们认知的,这里不再赘述。

图片

图片



04



写在最后

整体来说,Fastjson的功能还是很强大的,使用也比较简单。可能也正是因为Fastjson的容易使用,让我们忽略了对其内部工作原理的研究,未能关注到在特定使用场景下可能造成的“问题”。本文第一部分花了很大的篇幅去总结Fastjson的一些使用注意事项,并结合具体的验证代码以及跟Jackson的对比,希望能帮助大家充分理解Fastjson的使用,在具体场景下调整使用方法,灵活运用。第二部分简单分析了一下Fastjson的底层工作原理,关键点在于掌握Serializer/Deserializer的创建和执行。整体的代码结构还是比较统一和清晰的,设计巧妙,支持的功能很丰富。希望能帮助大家“深入”了解Fastjson,掌握更多的设计方法。


上面的学习和分析,也只是涵盖了Fastjson的少部分功能,很多的功能和细节还没有涉及到。以上这些都是根据自己的研究和理解所写,一家之言,如有不实之处,欢迎指正和交流,一起学习。

图片

图片

图片
欢迎留言一起参与讨论~

继续滑动看下一个
阿里技术
向上滑动看下一个