写点什么

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

作者:阿里技术
  • 2023-02-10
    浙江
  • 本文字数:27449 字

    阅读完需:约 90 分钟

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

作者:陶征策   阿里国际站商家技术团队


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 等数据结构的序列化。下面通过代码展示下。


@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 deserializationJackson: 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 使用起来会更方便。


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


上面的很大篇幅中,列举了使用 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 支持反序列化的核心函数有三个,他们的主要功能和区别如下:


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


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


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


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


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


  1. 如果字符串是 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 中就好了。


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


四、写在最后


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


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



发布于: 刚刚阅读数: 3
用户头像

阿里技术

关注

专注分享阿里技术的丰富实践和前沿创新。 2022-05-24 加入

阿里技术的官方号,专注分享阿里技术的丰富实践、前沿洞察、技术创新、技术人成长经验。阿里技术,与技术人一起创造成长与成就。

评论

发布
暂无评论
Fastjson踩“坑”记录和“深度”学习_Fastjson_阿里技术_InfoQ写作社区