本篇将主要介绍json序列化的详细流程。本文阅读的FastJSON源码版本为2.0.31。
在日常开发中,我们常用FastJSON进行序列化和反序列化。虽然它给我们带来了便捷,但其背后的原理往往被忽视,于是一个不小心就引发了很多血案,例如:
在不知其所以然的情况下,我每次使用起来也是胆战心惊的,比如抛出我经常遇到的两个问题:
1.序列化操作:JSON.toJSONString()方法
代码中许多地方都使用了JSON.toJSONString()方法打印日志,可能会遇到转换失败的情况,比如下面一段报错:
注:阿里巴巴开发规约中已经明确禁止在日志打印中直接用JSON工具将对象转换成String,所以大家还是尽量避免使用。
2.反序列化操作:JSON.parseObject()方法
当一个类中嵌套了多层内部类时,JSON.parseObject() 方法是否能够准确转换?
因此,我决定遵循“深入了解才能安心使用”的原则,阅读一下 FastJSON 的源码,以便更好地理解其原理和使用时需要注意的事项。
由于内容较长,本篇将主要介绍json序列化的详细流程。本文阅读的FastJSON源码版本为2.0.31。
FastJSON的源码结构如下,其中JSON类是源码的入口类,虽然看起来有很多方法,实则概括起来是四类方法:
FastJSON中几个关键的包为:annotation包、asm包、parser包、serializer包。
annotation包:该包主要定义了在使用FastJSON中用到的注解,如@JSONField注解定义元素序列化和反序列化的行为;@JSONType注解定义java类序列化和反序列化的行为。
asm包:一个字节码的文件,能够动态生成和修改 Java 字节码,通过使用 ASM,FastJSON 可以在运行时生成优化后的序列化和反序列化方法,从而提升性能。
parser包:反序列化时用到的类。所有的反序列化器都继承ObjectDeserializer
serializer包:序列化时用到的类。所有的序列化器都继承ObjectSerializer
2.1 整体流程
以JSON.toJSONString(),并且序列化一个JavaBean为例,整个方法序列化的时序图如下:
首先,用户调用JSON.toJSONString() 方法并且传入待序列化对象,随后执行以下序列化流程:
1.创建SerializeWriter 对象:SerializeWriter对象类似于StringBuilder ,但性能上做了许多优化,用来存储序列化过程中产生的字符串。
2.创建JSONSerializer对象:JSONSerializer对象提供序列化的一个入口,持有所有具体负责对象序列化工作类的引用。然后调用具体序列化器的write()函数进行序列化,这也是序列化的正式开始:- 关键在于SerializeConfig,SerializeConfig中维护一个IdentityHashMap存储不同的Java类及其对应的序列化器之间的关系,如果从IdentityHashMap中找到了对应的处理器则直接返回,否则执行第3步。
3.序列化器为 null 的情况:
4.写入数据: 获取到序列化写入器后,JSONSerializer 调用该写入器的 write() 方法开始将数据写入 SerializeWriter。
5.返回 JSON 字符串: 最后,SerializeWriter 将存储的字符串转换为最终的 JSON 字符串,并返回给用户。
通过这些步骤,用户最终得到了序列化后的 JSON 字符串。
2.2 详细流程
下面详细介绍一下具体的代码实现流程:
1.调用JSON.toJSONString(obj)方法后,最终会走到下面的重载函数,这个函数主要干了三件事:
a.创建SerializeWriter对象 out,用于储存在序列化过程中产生的数据。
b.创建JSONSerializer 对象serializer,该对象持有所有负责具体对象序列化工作类的引用。
c.将对象 object 解析成 string,并将结果写入到out的buffer中。public static String toJSONString(Object object,
SerializeConfig config,
SerializeFilter[] filters,
String dateFormat,
int defaultFeatures,
SerializerFeature... features)
{
SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);
try {
JSONSerializer serializer = new JSONSerializer(out, config);
if (dateFormat != null && dateFormat.length() != 0) {
serializer.setDateFormat(dateFormat);
serializer.config(SerializerFeature.WriteDateUseDateFormat, true);
}
if (filters != null) {
for (SerializeFilter filter : filters) {
serializer.addFilter(filter);
}
}
serializer.write(object);
return out.toString();
} finally {
out.close();
}
}
这个序列化方法实际并不是真正执行序列化操作,首先做序列化特性配置,然后追加序列化拦截器,开始执行序列化对象操作委托给了config对象查找。2.其中serializer对象的write方法如下,这个方法主要做了两件事:
a.通过对象的类获得对应的解析类ObjectSerializer。public final void write(Object object) {
if (object == null) {
out.writeNull();
return;
}
Class> clazz = object.getClass();
ObjectSerializer writer = getObjectWriter(clazz);
try {
writer.write(this, object, null, null, 0);
} catch (IOException e) {
throw new JSONException(e.getMessage(), e);
}
}
那么接下来就是搞懂怎么获得解析类,解析类又是怎么序列化对象的就可以了。3.获取对象的解析类
getObjectWriter方法会内部调用重载方法,且create参数为true。
a. 首先,先调用get(clazz)方法,查看IdentityHashMap中有没有已经注册的解析类,有则直接返回。b. 若没有,则会通过一系列判断以及create为true的条件走到createJavaBeanSerializer(clazz)方法中,创建对应的解析类并放入到IdentityHashMap中。public ObjectSerializer getObjectWriter(Class> clazz) {
return getObjectWriter(clazz, true);
}
public ObjectSerializer getObjectWriter(Class> clazz, boolean create) {
ObjectSerializer writer = get(clazz);
if (writer != null) {
return writer;
}
if (writer == null) {
String className = clazz.getName();
if(){
}
else{
if (create) {
writer = createJavaBeanSerializer(clazz);
put(clazz, writer);
}
}
}
if (writer == null) {
writer = get(clazz);
}
}
return writer;
}
4.createJavaBeanSerializer(clazz)方法创建解析类。这个方法会创建出JavaBeanSerializer对象和SerializeBeanInfo对象,SerializeBeanInfo主要是包含了java对象的字段和方法信息以及注释等,它决定了一个java对象序列化过程的输出。JavaBeanSerializer会根据SerializeBeanInfo对象中的fields字段,创建对应的成员变量的FieldSerializer。
public final ObjectSerializer createJavaBeanSerializer(Class> clazz) {
String className = clazz.getName();
SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy, fieldBased);
return createJavaBeanSerializer(beanInfo);
}
a.首先,通过TypeUtils获取变量beanInfo。i.最终会走到computeGetters方法通过getter拿到所有对象的属性信息。 public static SerializeBeanInfo buildBeanInfo(Class> beanType
, Map<String, String> aliasMap
, PropertyNamingStrategy propertyNamingStrategy
, boolean fieldBased
) {
Map<String, Field> fieldCacheMap = new HashMap<String, Field>();
ParserConfig.parserAllFieldToCache(beanType, fieldCacheMap);
List fieldInfoList = fieldBased
? computeGettersWithFieldBase(beanType, aliasMap, false, propertyNamingStrategy)
: computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, false, propertyNamingStrategy);
return new SerializeBeanInfo(beanType, jsonType, typeName, typeKey, features, fields, sortedFields);
}
ii.computeGetters方法的具体内容如下,通过判断以get开头和is开头的方法返回元素属性的集合。
public static List computeGetters(Class> clazz,
JSONType jsonType,
Map<String, String> aliasMap,
Map<String, Field> fieldCacheMap,
boolean sorted,
PropertyNamingStrategy propertyNamingStrategy
) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String methodName = method.getName();
if (methodName.startsWith("get")) {
};
if (methodName.startsWith("is")) {
};
}
}
b.然后创建java类的解析器,为了提升性能,默认通过asm动态生成类来避免重复执行时的反射开销。public ObjectSerializer createJavaBeanSerializer(SerializeBeanInfo beanInfo) {
boolean asm = this.asm && !fieldBased;
if (asm) {
try {
ObjectSerializer asmSerializer = createASMSerializer(beanInfo);
if (asmSerializer != null) {
return asmSerializer;
}
}
}
}
private final JavaBeanSerializer createASMSerializer(SerializeBeanInfo beanInfo) throws Exception {
JavaBeanSerializer serializer = asmFactory.createJavaBeanSerializer(beanInfo);
return serializer;
}
public JavaBeanSerializer createJavaBeanSerializer(SerializeBeanInfo beanInfo) throws Exception {
generateWriteMethod(clazz, mw, getters, context);
}
private void generateWriteMethod(Class> clazz, MethodVisitor mw, FieldInfo[] getters,
Context context) throws Exception {
mw.visitMethodInsn(INVOKESPECIAL, JavaBeanSerializer,
"write", "(L" + JSONSerializer
+ ";Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;I)V");
}
最终调用的还是JavaBeanSerializer的write方法写入。5.调用解析类的write方法序列化对象
首先会根据IgnoreNonFieldGetter属性选择是否忽略没有对应字段的get方法,然后通过调用每个属性序列化器的getPropertyValueDirect方法获得对应的值。
protected void write(JSONSerializer serializer,
Object object,
Object fieldName,
Type fieldType,
int features,
boolean unwrapped
) throws IOException {
final boolean ignoreNonFieldGetter = out.isEnabled(SerializerFeature.IgnoreNonFieldGetter);
if (notApply) {
propertyValue = null;
} else {
try {
propertyValue = fieldSerializer.getPropertyValueDirect(object);
}
}
}
可以看到,最终还是通过反射获取到属性的值加入到输出流中。public Object getPropertyValueDirect(Object object) throws InvocationTargetException, IllegalAccessException {
Object fieldValue = fieldInfo.get(object);
if (persistenceXToMany && !TypeUtils.isHibernateInitialized(fieldValue)) {
return null;
}
return fieldValue;
}
public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {
return method != null
? method.invoke(javaObject)
: field.get(javaObject);
}
FastJSON序列化元素时是通过调用对象的所有以“get”或者“is”开头的方法实现的,根据上文源码中实现逻辑,应该注意以下几点:
举个🌰:类MyTest定义了一个没有对应成员变量的getNoField()方法,并且方法里有错误代码,根据源码逻辑,序列化过程中一定会调用这个方法。
S@Data
class MyTest {
private String name;
public int value;
public int getNoField() {
return 1/0;
}
}
public class FastJsonTest {
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.setName("张三");
System.out.println(JSON.toJSONString(myTest));
}
}
测试代码输出如下:序列化失败,抛出ArithmeticException异常。Exception in thread "main" com.alibaba.fastjson.JSONException: write javaBean error, fastjson version 1.2.84, class MyTest.MyTest, method : getNoField
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:541)
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:154)
at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:312)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:793)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:731)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:688)
at MyTest.FastJsonTest.main(FastJsonTest.java:18)
Caused by: java.lang.ArithmeticException: / by zero
at MyTest.MyTest.getNoField(FastJsonTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.alibaba.fastjson.util.FieldInfo.get(FieldInfo.java:571)
at com.alibaba.fastjson.serializer.FieldSerializer.getPropertyValueDirect(FieldSerializer.java:143)
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:284)
... 6 more
SerializerFeature.IgnoreNonFieldGetter参数,则可以序列化成功。
JSON.toJSONString(myTest,SerializerFeature.IgnoreNonFieldGetter);
布尔类型的属性不要使用is开头,fastJons会截取is后面的名称当作属性名,造成序列化错误的情况。
举个🌰:自定义类MyTest中包含属性isActive和value,另外还写了一个没有对应成员变量的getNoField方法。
@Data
class MyTest {
private boolean isActive;
private boolean value;
public MyTest() {
}
public MyTest(boolean isActive, boolean value) {
this.isActive = isActive;
this.value = value;
}
public int getNoField() {
return 1;
}
}
public class FastJsonTest {
public static void main(String[] args) {
MyTest myTest = new MyTest(true, false);
myTest.setActive(true);
myTest.setValue(false);
System.out.println(JSON.toJSONString(myTest));
}
}
测试代码的输出结果为:属性"isActive"错误的输出了"active",还错误的输出了不存在属性noField。{"active":true,"noField":1,"value":false}
注:在阿里巴巴开发手册中已经专门强调任何布尔类型的变量都不要加is前缀,容易造成解析异常。(所以说开发手册还是要多看几遍!!)
序列化过程如果存在重复/循环引用的情况,FastJSON就会用引用标识$ref代替,这样做的好处一是可以节省空间,二是可以避免循环引用的问题。举个🌰:
public static void main(String[] args) {
User user1 = new User();
user1.setAge(18);
user1.setName("张三");
List list = new ArrayList<>();
list.add(user1);
list.add(user1);
System.out.println(JSON.toJSONString(list));
}
这段代码的输出结果如下:可以看到重复引用的user1变成了{"$ref":"$[0]"}格式。[{"age":18,"name":"张三"},{"$ref":"$[0]"}]
注意,这种字符串格式只有使用FastJSON才可以正确反序列化,当涉及到是有了不同的序列化框架时往往会导致失败,如果想要正常的序列化方式化,可以通过:
JSON.toJSONString(map,SerializerFeature.DisableCircularReferenceDetect)禁止使用循环引用。
这里给出序列化时常用到注解和SerializerFeature序列化属性。
最后,由于个人水平有限,如文章中有误解或疏漏之处,欢迎各位大佬批评指正~
注解 | 含义 | 参数 |
@JSONField | 该注解作用于方法上,字段上和参数上,可在进行序列化和反序列化时进行个性功能定制。 | |
@JSonType | 该注解作用于类上,对该类的字段进行序列化和反序列化时的特性功能定制。 | |
@JSONCreator | 用于标识一个类的构造函数或静态工厂方法,以便在反序列化 (将 JSON 数据转换为 Java 对象) 时使用。 | 无 |
@JSONPOJOBuilder | 用于定义POJO的构建器类和构建方法,使得创建复杂对象变得灵活。
| |
黄色标记的为常用枚举值。
常量 | 含义 |
QuoteFieldNames | 是否输出key值引号,默认为 true |
UseSingleQuotes | 输出是否使用单引号,默认为 false |
WriteMapNullValue | 是否输出值为 null 的字段,默认为 false |
WriteEnumUsingToString | 是否输出枚举值的 toString() 方法,默认为 false |
UseISO8601DateFormat | 是否使用 ISO8601 格式输出日期,默认为 false |
WriteNullListAsEmpty | 是否将 null 值的 List 输出为空数组,默认为 false |
WriteNullStringAsEmpty | 是否将 null 值的 String 输出为空字符串,默认为 false |
WriteNullNumberAsZero | 是否将 null 值的 Number 输出为 0,默认为 false |
WriteNullBooleanAsFalse | 是否将 null 值的 Boolean 输出为 false,默认为 false |
SkipTransientField | 是否跳过 transient 修饰的字段,默认为 false |
SortField | 是否按照字段名称排序输出,默认为 false |
WriteTabAsSpecial | 是否将制表符输出为 ,默认为 false |
PrettyFormat | 是否格式化输出,默认为 false |
WriteClassName | 是否输出对象的 class 名称,默认为 false |
DisableCircularReferenceDetect | 是否禁止循环引用检测,默认为 false |
WriteSlashAsSpecial | 是否将斜杠输出为 /,默认为 false |
BrowserCompatible | 是否输出为浏览器兼容的 JSON 格式,默认为 false |
WriteDateUseDateFormat | 是否按照指定的日期格式输出日期,默认为 false |
DisableCheckSpecialChar | 是否禁止特殊字符检测,默认为 false |
NotWriteDefaultValue | 是否不输出默认值字段,默认为 false |
BeanToArray | 是否将 JavaBean 转换为数组输出,默认为 false |
WriteNonStringKeyAsString | 是否将非 String 类型的 key 转换为 String 类型输出,默认为 false |
NotWriteRootClassName | 是否不输出根对象的 class 名称,默认为 false |
DisableASM | 是否禁用 ASM 库进行序列化,默认为 false |