雪球社区

让建站和SEO变得简单

让不懂建站的用户快速建站,让会建站的提高建站效率!

投资讨论你的位置:雪球社区 > 投资讨论 > 再也不精碎心裂胆地使用FastJSON了——序列化篇
再也不精碎心裂胆地使用FastJSON了——序列化篇

2024-11-09 12:54    点击次数:115

  

阿里妹导读本篇将主要先容json序列化的能干经由。本文阅读的FastJSON源码版块为2.0.31。

一、引子

在日常开辟中,咱们常用FastJSON进行序列化和反序列化。天然它给咱们带来了简短,但其背后的旨趣相似被淡薄,于是一个不预防就激发了许多血案,举例:

FastJSON 序列化踩坑纪录 - 类中get措施喧阗被实践;记一次FastJSON使用失当引起的线上Full GC问题排查;FastJSON激发的Full GC问题排查;急速 24 小时 —— 记一个 FastJSON 激发的小标准 bug 排查;FastJSON序列化机制 -- 排查JSON.toJSONString激发的bug;

在不知其是以然的情况下,我每次使用起来亦然惊恐万状的,比如抛出我不绝遭遇的两个问题:

1.序列化操作:JSON.toJSONString()措施

代码中许多方位齐使用了JSON.toJSONString()措施打印日记,可能会遭遇提拔失败的情况,比如底下一段报错:



注:阿里巴巴开辟规约中照旧明确退却在日记打印中径直用JSON用具将对象提拔成String,是以大家如故尽量幸免使用。



2.反序列化操作:JSON.parseObject()措施

当一个类中嵌套了多层里面类时,JSON.parseObject() 措施是否大约准确提拔?

因此,我决定顺从“潜入了解才智宽解使用”的原则,阅读一下 FastJSON 的源码,以便更好地交融其旨趣和使用时需要珍惜的事项。

由于内容较长,本篇将主要先容json序列化的能干经由。本文阅读的FastJSON源码版块为2.0.31。

二、FastJSON源码阅读

1. FastJSON举座结构

FastJSON的源码结构如下,其中JSON类是源码的进口类,天然看起来有许多措施,实则抽象起来是四类措施:

序列化措施:JSON.toJSONString(),复返字符串;JSON.toJSONBytes(),复返byte数组;反序列化措施:JSON.parseObject(),复返JsonObject;JSON.parse(),复返Object;JSON.parseArray(), 复返JSONArray;将JSON对象提拔为java对象:JSON.toJavaObject();将JSON对象写入write流:JSON.writeJSONString();



FastJSON中几个要津的包为:annotation包、asm包、parser包、serializer包。

annotation包:该包主要界说了在使用FastJSON顶用到的注解,如@JSONField注解界说元素序列化和反序列化的活动;@JSONType注解界说java类序列化和反序列化的活动。asm包:一个字节码的文献,大约动态生成和修改 Java 字节码,通过使用 ASM,FastJSON 不错在运行时生成优化后的序列化和反序列化措施,从而提高性能。parser包:反序列化时用到的类。通盘的反序列化器齐罗致ObjectDeserializerserializer包:序列化时用到的类。通盘的序列化器齐罗致ObjectSerializer

2. 序列化经由

2.1 举座经由

以JSON.toJSONString(),况且序列化一个JavaBean为例,通盘措施序列化的时序图如下:



领先,用户调用JSON.toJSONString() 措施况且传入待序列化对象,随后实践以下序列化经由:

1.创建SerializeWriter 对象:SerializeWriter对象访佛于StringBuilder ,但性能上作念了许多优化,用来存储序列化过程中产生的字符串。

2.创建JSONSerializer对象:JSONSerializer对象提供序列化的一个进口,捏有通盘具体负责对象序列化使命类的援用。然后调用具体序列化器的write()函数进行序列化,这亦然序列化的认真启动:

要津在于SerializeConfig,SerializeConfig中孤寒一个IdentityHashMap存储不同的Java类高出对应的序列化器之间的关联,如果从IdentityHashMap中找到了对应的处分器则径直复返,否则实践第3步。

3.序列化器为 null 的情况:

如果写入器为 null,参加创建 JavaBeanSerializer 的经由。SerializeConfig 调用 TypeUtils 的 buildBeanInfo() 措施来构建 Bean 信息。TypeUtils 狡计对应类的 getter 措施,并复返 SerializeBeanInfo 对象。SerializeConfig 使用 createASMSerializer 创建 ASM 序列化器。将新创建的序列化器存入 SerializeConfig。

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, // 全局序列化建立,孤寒一个IdentityHashMap存储不同的Java类高出对应的Serializer之间的关联 SerializeFilter[] filters, // 序列化禁绝器,定制序列化需求,如某个属性是否序列化 String dateFormat, // 序列化日历形式 int defaultFeatures, // 默许序列化特色 SerializerFeature... features) //自界说序列化特色{// out 对象保存阐述对象的恶果,储存在序列化过程中产生的数据,最终会提拔成 string SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);try {// 极端于序列化的组合器,捏有通盘负责具体对象序列化使命类的援用 JSONSerializer serializer = new JSONSerializer(out, config);if (dateFormat != null && dateFormat.length() != ) { serializer.setDateFormat(dateFormat); serializer.config(SerializerFeature.WriteDateUseDateFormat, true); }if (filters != null) {for (SerializeFilter filter : filters) {//添加禁绝器 serializer.addFilter(filter); } }// 将对象 object 阐述成 string,并将恶果写入到out的buffer中 serializer.write(object);returnout.toString(); } finally {out.close(); }}

这个序列化措施试验并不是果然实践序列化操作,领先作念序列化特色建立,然后追加序列化禁绝器,启动实践序列化对象操作寄予给了config对象查找。

2.其中serializer对象的write措施如下,这个措施主要作念了两件事:

a.通过对象的类赢得对应的阐述类ObjectSerializer。

b.调用阐述类的write措施序列化对象。

public final voidwrite(Object object) {if (object == null) {out.writeNull();return; } Class<?> clazz = object.getClass();/** * 获取到对应的阐述类,通盘的类齐收场了接口 ObjectSerializer * * 关于自界说的JavaBean类,从IdentityHashMap找不到会从头创建 JavaBeanSerializer 类并放入Map中 */ ObjectSerializer writer = getObjectWriter(clazz);try {// 使用具体serializer实例处分对象 writer.write(this, object, null, null, ); } catch (IOException e) {thrownew 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) {//领先从里面照旧注册查找特定class的序列化实例 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。

publicfinal 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 // ) {/*不详*/// fieldName,field ,先生成fieldName的快照,减少之后的findField的轮询Map<String, Field> fieldCacheMap = new HashMap<String, Field>(); ParserConfig.parserAllFieldToCache(beanType, fieldCacheMap); List<FieldInfo> fieldInfoList = fieldBased ? computeGettersWithFieldBase(beanType, aliasMap, false, propertyNamingStrategy) // : computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, false, propertyNamingStrategy);/*不详*/returnnew SerializeBeanInfo(beanType, jsonType, typeName, typeKey, features, fields, sortedFields); }

ii.computeGetters措施的具体内容如下,通过判断以get发轫和is发轫的措施复返元素属性的会聚。

public static List<FieldInfo> 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; } }/*不详*/ }}privatefinal 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);/*不详*/}privatevoidgenerateWriteMethod(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措施赢得对应的值。

protectedvoid 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)) {returnnull; }return fieldValue;}//fieldInfo.get(object)措施public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {return method != null ? method.invoke(javaObject) : field.get(javaObject);}



三、序列化珍惜事项

1. getter方轨则范化

FastJSON序列化元素时是通过调用对象的通盘以“get”或者“is”发轫的措施收场的,字据上文源码中收场逻辑,应该珍惜以下几点:

要序列化的元素必须有对应的getter措施。getter措施中不要有过于复杂的逻辑,最佳只读不修改,幸免在某规律列化过程中导致数据对象变更。引子中抛出的第一个问题亦然在序列化时,由于某个get措施抛出很是导致的。非对应成员变量的措施尽量不要以get发轫,否则很容易形成踩坑或者形成序列化后新增了一个莫得的属性的问题,如果实在要用的时间不错使用注解@JSONField(serialize = false)淡薄该措施,或者在序列化的时间加上SerializerFeature.IgnoreNonFieldGetter参数,忽略掉通盘莫得对应成员变量(Field)的getter函数。

举个🌰:类MyTest界说了一个莫得对应成员变量的getNoField()措施,况且措施里有失实代码,字据源码逻辑,序列化过程中一定会调用这个措施。

S@DataclassMyTest {private String name;publicintvalue;publicintgetNoField() {return1/; }}publicclassFastJsonTest {publicstaticvoidmain(String[] args) { MyTest myTest = new MyTest(); myTest.setName("张三"); System.out.println(JSON.toJSONString(myTest)); }}

测试代码输出如下:序列化失败,抛出ArithmeticException很是。

Exceptionin thread "main" com.alibaba.fastjson.JSONException: write javaBean error, fastjson version 1.2.84, class MyTest.MyTest, method : getNoFieldatcom.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:541)atcom.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:154)atcom.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:312)atcom.alibaba.fastjson.JSON.toJSONString(JSON.java:793)atcom.alibaba.fastjson.JSON.toJSONString(JSON.java:731)atcom.alibaba.fastjson.JSON.toJSONString(JSON.java:688)atMyTest.FastJsonTest.main(FastJsonTest.java:18)Causedby: java.lang.ArithmeticException: / by zeroatMyTest.MyTest.getNoField(FastJsonTest.java:29)atsun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)atjava.lang.reflect.Method.invoke(Method.java:498)atcom.alibaba.fastjson.util.FieldInfo.get(FieldInfo.java:571)atcom.alibaba.fastjson.serializer.FieldSerializer.getPropertyValueDirect(FieldSerializer.java:143)atcom.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:284)...6 more

如果序列化措施加入:

SerializerFeature.IgnoreNonFieldGetter参数,则不错序列化生效。

JSON.toJSONString(myTest,SerializerFeature.IgnoreNonFieldGetter);

2. 布尔类型属生定名不要用is发轫

布尔类型的属性不要使用is发轫,fastJons会截取is背面的称呼看成属性名,形成序列化失实的情况。

举个🌰:自界说类MyTest中包含属性isActive和value,另外还写了一个莫得对应成员变量的getNoField措施。

@DataclassMyTest {private boolean isActive;private boolean value;publicMyTest() { }publicMyTest(boolean isActive, boolean value) {this.isActive = isActive;this.value = value; }publicintgetNoField() {return1; }}publicclassFastJsonTest {publicstaticvoidmain(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前缀,容易形成阐述很是。(是以说开辟手册如故要多看几遍!!)



3. 重叠援用问题

序列化过程如果存在重叠/轮回援用的情况,FastJSON就会用援用标志$ref代替,这么作念的平正一是不错省俭空间,二是不错幸免轮回援用的问题。举个🌰:

publicstaticvoidmain(String[] args){ User user1 = new User(); user1.setAge(18); user1.setName("张三"); List<User> 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序列化属性。

终末,由于个东谈主水平有限,如著述中有诬蔑或缓慢之处,接待诸君大佬月旦指正~

1.注解

2.SerializerFeature成列类

黄色记号的为常用成列值。



Powered by 雪球社区 @2013-2022 RSS地图 HTML地图

Copyright Powered by站群系统 © 2013-2024