fastjson源码解析——反序列化(三)

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信息有关的类JavaBeanInfobuild函数,传递了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对象里,返回以供调用。

在我分析的过程中,我注意到这个方法很长,但是却很容易读懂,我感觉为了提高程序可读性,阿里巴巴的开发者采用了几个方法

  1. 良好的程序缩进:在翻看漫长的代码时,我发现我可以凭借IDE中代码行开始时的缩进情况,下意识地判断出这段代码的归属,这就是开发者良好的代码缩进习惯。我以前听说过,也使用过阿里巴巴规范Java代码的标准来约束自己的代码,发现原来按照自己习惯缩进,对于其他同学难懂的程序可读性也提高了不少。
  2. 采用意义命名法、小驼峰的代码命名方法:老生常谈,但这个习惯很重要。对于我,代码中变量命名都按照规范,所以可读性相对也高一些,但是看部分同学代码时常常询问胡乱命名的变量的意义,效率不高。可见不规范的命名,对代码可读性简直是毁灭性的打击。
  3. 好的工具:这个主要是IDE的选择,IDEA在Java的IDE中可以算是登峰造极,非常好用,代码的区分很明显。

看过这些代码,我领略了代码开发规范的重要性,以后也想更严格地执行这些规范。

最后

fastjson反序列化的部分暂时介绍到这里。本文从fastjson对无现存反序列化实例的具体类型的实例创建开始,介绍到如何通过java.lang.reflect提供的方法获取到Getter/Setter。
在本次旅途中,我们接触到JavaBean对get、set函数的获取方法,在下一节我也会特别介绍这个方法内如何对不同类别的类获取方法。同时,我们也可以看到阿里巴巴严格的代码规范,透过长长代码,它的可读性依然很高,反观我们的程序,100+行的方法,就极可能出现胡乱命名、不遵守大小驼峰命名的情况,可读性下降极快。我们也应该学习编写代码的技巧、规范化的要求,严格规范自己的代码,减少维护的工作量。
下期,我会出一个特辑,专门介绍如何对不同类型的类获取Getter/Setter方法。
感谢各位老师的阅读与指导!

发表评论