2021SC@SDUSC
本文在CSDN个人博客同步发出,地址CSDN博客
概要
上篇fastjson源码解析——反序列化(二)详细介绍了fastjson对具体类型的反序列化实例查找方法。从上文中,读者可以体悟出阿里巴巴开发者的巧思。
本文就从具体类型的反序列化实例查找过程中,对创建满足JavaBean
要求的类的反序列化实例开始,进入createJavaBeanDeserializer
方法,继续我们的fastjson反序列化之旅。
正式开始
进入
deserializer = createJavaBeanDeserializer(clazz, type)
方法
为了便于读者阅读,方法编号继承于上篇fastjson源码解析——反序列化(二)
6. ObjectDeserializer createJavaBeanDeserializer(Class<?> clazz, Type type)
这个方法意义非凡,因为项目中,开发者平时自己编写的类有一大部分都不继承jdk包含的类(除了Object)。这个方法实现了这部分反序列化需求;除此之外,这个方法构建了普通类的反序列化框架(调用了其他更具体的方法)。
public ObjectDeserializer createJavaBeanDeserializer(Class<?> clazz, Type type) {
//这一部分是验证asm开启状态的代码,对于我们简单的了解无帮助且无干扰,直接忽略。
boolean asmEnable = this.asmEnable & !this.fieldBased;
if (asmEnable) {
//忽略了asm开启状态下的反序列化方法
}
//这个分支是重头戏,他调用了新的方法
if (!asmEnable) {
return new JavaBeanDeserializer(this, clazz, type);
}
/**
* 忽略构建基于asm的反序列化对象部分代码
*/
}
对于自定义类反序列化,如果没有开启asm
的情况下,会使用JavaBeanDeserializer
进行反序列化转换,这里有意屏蔽基于asm直接操纵字节码实现。
接下来我们进入com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer
的构造函数,看看这个new
到底干了什么
7. constructor of JavaBeanDeserializer
按住Ctrl,点击new
进入构造函数
public JavaBeanDeserializer(ParserConfig config, Class<?> clazz, Type type){
this(config //
, JavaBeanInfo.build(clazz, type, config.propertyNamingStrategy, config.fieldBased, config.compatibleWithJavaBean, config.isJacksonCompatible())
);
}
可以看出,这个构造函数仍然是调用了其他的构造函数,能得知这个构造函数传递了一个配置config
,还调用了一个与JavaBean信息有关的类JavaBeanInfo
的build
函数,传递了JavaBean相关的参数与命名方法配置。
我们继续点击进入另外一个构造函数
public JavaBeanDeserializer(ParserConfig config, JavaBeanInfo beanInfo){
this.clazz = beanInfo.clazz;//保存类的信息 clazz
this.beanInfo = beanInfo;//JavaBean的关键信息,后续继续介绍
ParserConfig.AutoTypeCheckHandler autoTypeCheckHandler = null;
if (beanInfo.jsonType != null && beanInfo.jsonType.autoTypeCheckHandler() != ParserConfig.AutoTypeCheckHandler.class) {
try {
autoTypeCheckHandler = beanInfo.jsonType.autoTypeCheckHandler().newInstance();
//尝试调用beanInfo里存储的关于json类型的实例
} catch (Exception e) {
//
}
}
this.autoTypeCheckHandler = autoTypeCheckHandler;
Map<String, FieldDeserializer> alterNameFieldDeserializers = null;
sortedFieldDeserializers = new FieldDeserializer[beanInfo.sortedFields.length];//确认需要反序列化的Field长度
for (int i = 0, size = beanInfo.sortedFields.length; i < size; ++i) {
/* 根据每一个需要反序列化的Field,确定其具体类型 */
FieldInfo fieldInfo = beanInfo.sortedFields[i];
FieldDeserializer fieldDeserializer = config.createFieldDeserializer(config, beanInfo, fieldInfo);
sortedFieldDeserializers[i] = fieldDeserializer;
if (size > 128) {
if (fieldDeserializerMap == null) {
fieldDeserializerMap = new HashMap<String, FieldDeserializer>();
}
fieldDeserializerMap.put(fieldInfo.name, fieldDeserializer);
}
for (String name : fieldInfo.alternateNames) {
if (alterNameFieldDeserializers == null) {
alterNameFieldDeserializers = new HashMap<String, FieldDeserializer>();
}
alterNameFieldDeserializers.put(name, fieldDeserializer);
}
}
this.alterNameFieldDeserializers = alterNameFieldDeserializers;
fieldDeserializers = new FieldDeserializer[beanInfo.fields.length];
for (int i = 0, size = beanInfo.fields.length; i < size; ++i) {
FieldInfo fieldInfo = beanInfo.fields[i];
FieldDeserializer fieldDeserializer = getFieldDeserializer(fieldInfo.name);
//根据field类型查找相对于的反序列器,放入数组,准备开始最后的反序列化操作
fieldDeserializers[i] = fieldDeserializer;
}
}
这个方法,使用传入的beanInfo
明确每一个需要反序列化的field
的具体反序列化器,准备执行最后的反序列化操作,即调用field
对应的Getter/Setter函数对每一个field
传入相关的值。
在我分析代码的时候,我注意到这个构造函数调用了大量与beanInfo
相关的参数,这个beanInfo
到底是什么。为了解决这个问题,我们回过头,来看JavaBeanInfo.build(clazz, type, config.propertyNamingStrategy, config.fieldBased, config.compatibleWithJavaBean, config.isJacksonCompatible())
这个方法的执行过程。
8. public static JavaBeanInfo build()
这个静态方法build()
特别重要,因为它涉及到对JavaBean底层的操作,调用了相关方法,查询到传入的这个类的所有符合JavaBean要求的Getter/Setter,将其保存在this
对象里,传给JavaBeanDeserializer
的构造函数以供使用。
接下来请看代码(巨长,700+行),我截取其中重要的一部分展开分析
首先是方法的架构
public static JavaBeanInfo build(Class<?> clazz //
, Type type //
, PropertyNamingStrategy propertyNamingStrategy //
, boolean fieldBased //
, boolean compatibleWithJavaBean
, boolean jacksonCompatible
) {
//明确类的类型:kotlin部分的类?接口?抽象类?。。。
//检索所有的annotation,优先按照annotation的内容进行特异化处理star
//获取到Getter和Setter函数名,筛选需要的函数,保存起来
//对不同标准的Getter/Setter开展不同的处理:
// 比如setAge()和setage()这两个方法如何处理,是否视为对同一个属性age的处理
//对Getter也进行同样的处理
//保存信息,返回
这个方法虽然说有700+行,如此恐怖的代码量,实际上并不是很难读懂,大部分代码都在做一项工作:把Java繁多复杂的类的状态(接口、抽象类等)区分开,采用对应的标识符,
这个架构思路清晰,先区分不同的类,将接口、抽象类和实体类区分开,也将kotlin的类和普通Java类分开,便于后续对Getter/Setter进行区分,保证准确性,不会把Age
变量的值赋给age
。
使用java.lang.reflect
(jdk提供)里提供的方法,查询出传入类型所有的get/set方法,并且查询其他方法是否为static
,采用不同的处理方式。
将数据保存在this
对象里,返回以供调用。
在我分析的过程中,我注意到这个方法很长,但是却很容易读懂,我感觉为了提高程序可读性,阿里巴巴的开发者采用了几个方法
- 良好的程序缩进:在翻看漫长的代码时,我发现我可以凭借IDE中代码行开始时的缩进情况,下意识地判断出这段代码的归属,这就是开发者良好的代码缩进习惯。我以前听说过,也使用过阿里巴巴规范Java代码的标准来约束自己的代码,发现原来按照自己习惯缩进,对于其他同学难懂的程序可读性也提高了不少。
- 采用意义命名法、小驼峰的代码命名方法:老生常谈,但这个习惯很重要。对于我,代码中变量命名都按照规范,所以可读性相对也高一些,但是看部分同学代码时常常询问胡乱命名的变量的意义,效率不高。可见不规范的命名,对代码可读性简直是毁灭性的打击。
- 好的工具:这个主要是IDE的选择,IDEA在Java的IDE中可以算是登峰造极,非常好用,代码的区分很明显。
看过这些代码,我领略了代码开发规范的重要性,以后也想更严格地执行这些规范。
最后
fastjson反序列化的部分暂时介绍到这里。本文从fastjson对无现存反序列化实例的具体类型的实例创建开始,介绍到如何通过java.lang.reflect
提供的方法获取到Getter/Setter。
在本次旅途中,我们接触到JavaBean对get、set函数的获取方法,在下一节我也会特别介绍这个方法内如何对不同类别的类获取方法。同时,我们也可以看到阿里巴巴严格的代码规范,透过长长代码,它的可读性依然很高,反观我们的程序,100+行的方法,就极可能出现胡乱命名、不遵守大小驼峰命名的情况,可读性下降极快。我们也应该学习编写代码的技巧、规范化的要求,严格规范自己的代码,减少维护的工作量。
下期,我会出一个特辑,专门介绍如何对不同类型的类获取Getter/Setter方法。
感谢各位老师的阅读与指导!