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

2021SC@SDUSC

本文在CSDN个人博客同步发出,地址CSDN博客

概要

上文fastjson源码解析——反序列化(八)我们深入探索了针对规定了每个元素类型的JSON对象数组执行反序列化的操作顺序,使用大量对比,将单一类型数组反序列化的API与规定了类型的API比较,探索这两个重点API内部对JSON字符串、token的使用等等异同点:单一类型的API内部逻辑较简单,可以看到一些融合度较高的方法,也可以直接看到对token的使用;规定各元素类型的API逻辑相对复杂,且具有完备的程序层次结构,不会在代码浅层看到对token的使用,更多在进行结构的规定,减少程序错误输入对某个元素反序列化的影响。
本文将继续从规定各元素类型的JSON对象数组反序列化方法入手,探究其在parseArray(String, Type[], ParserConfig)方法的

parser.handleResovleTask(list);

方法调用及其内部的逻辑

1. handleResovleTask(Object)方法

这个方法的返回值是void,同时可以看到内部实质上是通用反序列化器parserObject(也就是上文提到的list)内部进行调整。
先看代码

    public void handleResovleTask(Object value) {
        if (resolveTaskList == null) {
        // 防止传入空值导致后续操作出现未预料到的空指针异常
            return;
        }

        for (int i = 0, size = resolveTaskList.size(); i < size; ++i) {
            ResolveTask task = resolveTaskList.get(i); // 获取到parser的成员列表中的元素
            String ref = task.referenceValue; // 保存操作的依据(值)

            Object object = null;
            if (task.ownerContext != null) {
                object = task.ownerContext.object;
            }

            Object refValue;

            if (ref.startsWith("$")) {
                refValue = getObject(ref);
                if (refValue == null) {
                    try {
                        JSONPath jsonpath = new JSONPath(ref, SerializeConfig.getGlobalInstance(), config, true);
                        if (jsonpath.isRef()) {
                        // 判断是否需要修改默认反序列化操作得到的结果列表
                            refValue = jsonpath.eval(value);
                        }
                    } catch (JSONPathException ex) {
                        // skip
                    }
                }
            } else {
                refValue = task.context.object;
            }

            FieldDeserializer fieldDeser = task.fieldDeserializer;

            if (fieldDeser != null) {
                if (refValue != null
                        && refValue.getClass() == JSONObject.class
                        && fieldDeser.fieldInfo != null
                        && !Map.class.isAssignableFrom(fieldDeser.fieldInfo.fieldClass)) {
                    Object root = this.contextArray[0].object;
                    JSONPath jsonpath = JSONPath.compile(ref);
                    if (jsonpath.isRef()) {
                        refValue = jsonpath.eval(root);
                    }
                }

                // workaround for bug
                if (fieldDeser.getOwnerClass() != null
                        && (!fieldDeser.getOwnerClass().isInstance(object))
                        && task.ownerContext.parent != null
                ) {
                    for (ParseContext ctx = task.ownerContext.parent;ctx != null;ctx = ctx.parent) {
                        if (fieldDeser.getOwnerClass().isInstance(ctx.object)) {
                            object = ctx.object;
                            break;
                        }
                    }
                }

                fieldDeser.setValue(object, refValue);
            }
        }
    }

这个方法的核心思路在于,根据通用反序列化器内部存储的列表,对执行了默认反序列化操作的JSON对象(存放于list中)进行深入检查,判断是否要修改数据列表的值。若不需要修改,则不动传入的list对象。如果需要修改,就根据反序列化器内部存储的依据值,对列表进行修改。
可以看到,在这个方法里出现了一个类型ResolveTask,接下来我们进入这个类,查看此类型内部的数据存储及方法,帮助我们明确fastjson使用这个类执行哪些操作。

2. class ResolveTask

代码:

    public static class ResolveTask {

        public final ParseContext context;
        public final String       referenceValue;
        public FieldDeserializer  fieldDeserializer;
        public ParseContext       ownerContext;

        public ResolveTask(ParseContext context, String referenceValue){
            this.context = context;
            this.referenceValue = referenceValue;
        }
    }

这个类是内部类,主要是保存一些有针对性的数据。针对JSON对象数组中的每一个元素所规定的对象类型,ResolveTask类保存这个类型的反序列化数据,内部数据为public,公开给其他方法,可直接查看数据,提供某个特定类型调整数据所需的参数、标志等。
作为一个存放数据的类型,ResolveTask并不能对外提供任何方法,它只有一个带参数的构造函数。但是这个类的设计,从他的数据可见性设置,到构造函数,包括它内部类的属性,可以看出fastjson开发者对程序可读性、简洁性的独特考量。

  1. 内部类:设置这个类为内部类,而不是另外建立一个包内的类,说明这个类只会在DefaultJSONParser类内使用,其他类完全不可见,这样从根本上杜绝其他类非法调用/误调用ResolveTask类的内容
  2. ResolveTask类的数据可见性:所有数据都是public可见性,对所有调用可见。如果这个类是一个普通的类,它的数据可见性设置为public是一个非常危险、极不安全的域。开发者的巧思就在其中,这个类是通用反序列化器的内部类,能够看到、调用它的只可能是通用反序列化器这一个类,本来不安全的权限设置,反而成了ResolveTask类的优势,数据完全暴露给调用者,且数据不会暴露给其他任何类。这对于一个专门保存数据的类而言,既保证了数据的安全性、调用的合法性,也保证了调用数据的性能(getter方法获取数据、setter方法设置数值都属于方法调用,或多或少存在为了安全性牺牲了方法调用的开销),采用可直接赋值的方式。

我们可以看到,fastjson的开发者巧妙地设置了需要的类型的位置、数据可见性等等,尽可能提高性能的同时,保证了安全性,一举两得。

最后

本文对指定各元素类型的API内部逻辑parser.handleResovleTask(list)开始,查看fastjson对反序列化的结果列表的修正方式。针对不同类型的元素,都保存了一个ResolveTask类型的对象实例,存放这个类型的反序列化所需对象、数据等。
随着parseObject(), parseArray()这两个主要的操作分析进入尾声,我们一学期的fastjson源码解析之旅也接近终点,下次我将开始总结对象反序列化、数组反序列化的操作逻辑,从一个大的角度分析fastjson总体结构。
感谢各位老师的阅读与指导!

发表评论