前面讲了进行对象解析的两个方面,并讲了针对outWriter将不同类型的数据信息写到buf字符数组。本篇讲解对象解析的过程,即如何将不同类型的对象解析成outWriter所需要的序列信息。并考虑其中的性能优化。
取得解析器
首先我们需要取得指定对象的json序列化器,以便使用特定的序列化器来序列化对象。因此,需要有一个方法来取得相对应的序列化器。在fastjson中,使用了一个类似map的结构来保存对象类型和及对应的解析器。对于对象类型,在整个fastjson中,分为以下几类:
1 基本类型以及其包装类型,字符串
2 基本类型数组以及包装类型数组
3 Atomic类型
4 JMX类型
5 集合类型以及子类
6 时间类型
7 json类型
8 对象数组类型
9 javaBean类型
对于第1,2,3,4类型,在fastjson中使用了一个全局的单态实例来保存相对应的解析器;第5类型,处理集合类型,对于集合类型及其,由于其处理逻辑均是一样,所以只需要针对子类作一些的处理,让其返回相对应的集合类型解析器即可;第6类型,时间处理器,将时间转化为类似yyyy-MM-ddTHH:mm:ss.SSS的格式;第7类型,处理fastjson专有jsonAwre类型;第8类型,处理对象的数组形式,即处理数组时,需要考虑数组中的统一对象类型;第9,即处理我们最常使用的对象,javaBean类型,这也是在项目中解析得最多的类型。
我们要看一下相对应的取解析器的方法,即类JsonSerializer.getObjectWriter(Class<?> clazz)方法,参考其中的实现:
- public ObjectSerializer getObjectWriter(Class<?> clazz) {
- ObjectSerializer writer = mapping.get(clazz);
- if (writer == null) {
- if (Map.class.isAssignableFrom(clazz)) {
- mapping.put(clazz, MapSerializer.instance);
- } else if (List.class.isAssignableFrom(clazz)) {
- mapping.put(clazz, ListSerializer.instance);
- } else if (Collection.class.isAssignableFrom(clazz)) {
- mapping.put(clazz, CollectionSerializer.instance);
- } else if (Date.class.isAssignableFrom(clazz)) {
- mapping.put(clazz, DateSerializer.instance);
- } else if (JSONAware.class.isAssignableFrom(clazz)) {
- mapping.put(clazz, JSONAwareSerializer.instance);
- } else if (JSONStreamAware.class.isAssignableFrom(clazz)) {
- mapping.put(clazz, JSONStreamAwareSerializer.instance);
- } else if (clazz.isEnum()) {
- mapping.put(clazz, EnumSerializer.instance);
- } else if (clazz.isArray()) {
- Class<?> componentType = clazz.getComponentType();
- ObjectSerializer compObjectSerializer = getObjectWriter(componentType);
- mapping.put(clazz, new ArraySerializer(compObjectSerializer));
- } else if (Throwable.class.isAssignableFrom(clazz)) {
- mapping.put(clazz, new ExceptionSerializer(clazz));
- } else {
- mapping.put(clazz, new JavaBeanSerializer(clazz));
- }
- writer = mapping.get(clazz);
- }
- return writer;
- }
首先,取对象解析器是由一个类型为JSONSerializerMap的对象mapping中取。之所以使用此类型,这是性能优化的一部分,此类型并不是一个完整的map类型,只是实现了一个类似map的操作的类型。其内部并没有使用基于equals的比较方式,而是使用了System.identityHashCode的实现。对于一个对象,其identityHashCode的值是定值,而对于一个类型,在整个jvm中只有一个,因此这里使用基于class的identityHashCode是可以了,也避免了使用class的euqlas来进行比较。
接着再根据每一个类型从mapping中取出相对应的解析器。首先从继承而来的全局解析器取得解析器,如果对象不属于第1,2,3,4类型,而开始进入以下的if else阶段。
我们从上面的源码中,可以看出解析器主要分为两个部分,一个是与解析类型相关的,一个是无关的。比如对于第5,6,7类型,其中最5类型是集合类型,由于不知道集合类型中的具体类型(因为存在继承关系),所以类型无关。对于第8,9类型,其中第8类型为对象数组类型,对于对象数组,数组中的每一个对象的类型都是确定的,且整个数组只有一种类型,因此可以确定其类型,这时候就要使用类型相关解析器了,对于第9类型,需要使用解析对象的类型来确定相对应的javaBean属性,因此是类型相关。
另外一个确定解析器的过程当中,使用了映射机制,即将当前解析器与对应的类型映射起来,以便下一次时使用。对于集合类型及子类型,由于当前类型并不是确定的List或Collection类型,因此将当前类型与集合解析器映射起来。对于对象类型,需要将当前类型传递给相对应的解析器,以确定具体的属性。
解析过程
解析方法由统一的接口所定义,每个不同的解析器只需要实际这个方法并提供各自的实现即可。此方法为write(JSONSerializer serializer, Object object),由ObjectSerializer提供。带两个参数,第一个参数,即为解析的起点类jsonSerializer,此类封装了我们所需要的outWriter类,需要时只需要从此类取出即可。第二个参数为我们所要解析的对象,在各个子类进行实现时,可将此对象通过强制类型转换,转换为所需要的类型即可。
具体的解析过程根据不同的数据类型不所不同,对于第1,2类型,由于在outWriter中均有相对应的方法,所以在具体实现时,只需要调用相应的outWriter方法即可,而不需要作额外的工作。比如对于字符串解析器,它的解析过程如下所示:
- SerializeWriter out = serializer.getWrier();
- String value = (String) object;
- if (serializer.isEnabled(SerializerFeature.UseSingleQuotes)) {
- out.writeStringWithSingleQuote(value);
- } else {
- out.writeStringWithDoubleQuote(value);
- }
即首先,取得输出时所需要的outWriter,然后再根据配置决定是输出单引号+字符串还是双引号+字符串。
而对于其它并没有由outWriter所直接支持的类型而言,解析过程就相对于复杂一些。但是总的逻辑还是基于以下逻辑:
- 基于数据类型特点输出所特有的字符包装内容
- 基于数据类型特点转换为outWriter所能识别的内容
- 逐步解析,将对象解析产生的字符数组输出到outWriter中
只需此三步,即可完美的解析一个对象。因此,我们可以参考其中的一个具体实现,并讨论其中的具体优化措施。
在取得解析器方法getObjectWriter(Class<?> clazz)中,我们可以看到,对于集合类型中的Collection和List,fastjson是分开进行解析的。即两者在解析时在细微处有着不一样的实现。两者的区别在于List是有序的,可以根据下标对元素进行访问,对于常用List实现,ArrayList,使用下标访问子元素的价格为O1。这就是在fastJson中采取的一点优化措施,详细看以下实现代码:
- public final void write(JSONSerializer serializer, Object object) throws IOException {
- SerializeWriter out = serializer.getWrier();//取得输出器
- List<?> list = (List<?>) object;//强制转换为所需类型
- final int size = list.size();
- int end = size - 1;//此处定义为size-1,是因为对最后一位有特殊处理
- //空集合判断,省略之
- out.append('[');//集合前缀包装
- /** 以下代码使用get(X)方法访问下标,实现代码对于ArrayList实现有好处,对于LinkedList是否有好处,还待考虑 */
- for (int i = 0; i < end; ++i) {
- Object item = list.get(i);
- //空值判断
- Class<?> clazz = item.getClass();
- if (clazz == Integer.class) {//针对Integer.class特殊优化,使用outWriter自带方法
- out.writeIntAndChar(((Integer) item).intValue(), ',');
- } else if (clazz == Long.class) {//针对Long.class特殊优化,使用outWriter自带方法
- long val = ((Long) item).longValue();
- out.writeLongAndChar(val, ',');
- } else {
- serializer.write(item);//递归调用,写集合内元素
- out.append(',');//间隔符
- }
- }
- /** 以下代码为最后一位优化,当为最后一位时,不再需要输出间隔符,而是输出后缀包装符
- 这里即在处理时,直接输出后缀,与前面输出间隔符相对应 */
- Object item = list.get(end);
- Class<?> clazz = item.getClass();
- if (clazz == Integer.class) {
- out.writeIntAndChar(((Integer) item).intValue(), ']');
- } else if (clazz == Long.class) {
- out.writeLongAndChar(((Long) item).longValue(), ']');
- } else {
- serializer.write(item);
- out.append(']');
- }
- }
以下实现与collection相比不同的即在于处理中间元素与末尾元素的区别。相对于Collection,就不能使用以上的方法了,在实现上,就只能先输出前缀,再一个一个地处理里面的元素,最后输出后缀。此实现如下所示:
- public void write(JSONSerializer serializer, Object object) throws IOException {
- SerializeWriter out = serializer.getWrier();
- Collection<?> collection = (Collection<?>) object;
- out.append('[');
- boolean first = true;
- for (Object item : collection) {
- if (!first) {out.append(',');}
- first = false;
- Class<?> clazz = item.getClass();
- //Integer.class和Long.class特殊处理
- serializer.write(item);
- }
- out.append(']');
- }
以上代码就是通常最常见的实现了。
相对于集合类型实现,map实现和javaBean实现相对来说,稍微复杂了一点。主要是输出key和value的问题。在fastjson中,key输出表现为使用outWriter的writeKey来进行输出,value输出则同样使用常规的输出。按照我们先前所说的逻辑,首先还是输出包装字符内容,即{,在末尾输出}。然后,再根据每个key-value映射特点,采取相对应的输出方式。
当然,对于map类型输出和javaBean输出还是不一样的。两者可以互相转换,但fastjson在输出时还是采取了不一样的输出方式。那么,我们以源代码来查看相应的实现:
- public void write(JSONSerializer serializer, Object object) throws IOException {
- SerializeWriter out = serializer.getWrier();
- Map<?, ?> map = (Map<?, ?>) object;
- out.write('{');//前缀
- Class<?> preClazz = null;//缓存前一个value类型和相对应的解析器,减少类型判断解析
- ObjectSerializer preWriter = null;
- boolean first = true;
- for (Map.Entry<?, ?> entry : map.entrySet()) {
- //此处有删除,即根据nameFilter和valueFilter针对key-value作转换处理
- if (!first) {out.write(',');}//输出间隔符
- serializer.writeFieldName(key);//输出字段名+冒号
- first = false;
- Class<?> clazz = value.getClass();
- if (clazz == preClazz) {//此处即细节优化内容,直接使用前一个解析器,避免再次从jsonSerializer中查找
- preWriter.write(serializer, value);
- } else {
- /** 此处则就需要从jsonSerializer中查找解析器,并输出了 */
- preClazz = clazz;
- preWriter = serializer.getObjectWriter(clazz);
- preWriter.write(serializer, value);
- }
- }
- out.write('}');//后缀
- }
由上可以看出,map的实现还是按照我们常用的思路在进行。在性能优化处,采取了两个优化点,一是输出字段时,并不直接输出字段名,而是采取字段+冒号的方式一起输出,减少outWriter扩容计算。二是在查找value解析器时,尽量使用前一个value的解析器,避免重复查找。
相比map,javaBean的实现就相对更复杂。javaBean输出并不是采取key-value的方式,而是采取类似fieldSerializer的输出方式,即将属性名和值,组合成一个字段,一起进行输出。当然输出时还是先输出字段名,再输出值的实现。那么对于javaBean实现,首先要取得当前对象类型的所有可以输出的类型。
在fastjson实现中,并没有采取javaBean属性的读取方式,而是采取了使用getXXX和isXXX方法的读取模式,来取得一个类型的可读取属性。作为性能优化的一部分,读取属性的操作结果被缓存到了getter缓存器中。其实,并不是缓存到了getter缓存器中,只是该类型的javaBean序列化器对象被缓存到了jsonSerializer的对象类型-序列化器映射中。对于同一个类型,就不需要再次解析该类型的属性了。
有了相对应的字段,那么在实现时,就按照相应的字段进行输出即可。以下为实现代码:
- public void write(JSONSerializer serializer, Object object) throws IOException {
- SerializeWriter out = serializer.getWrier();
- out.append('{');//前缀
- for (int i = 0; i < getters.length; ++i) {
- FieldSerializer getter = getters[i];//取属性解析器
- Object propertyValue = getter.getPropertyValue(object);//取值
- //省略中间nameFilter和valueFilter过滤处理
- if (commaFlag) {out.append(',');}//间隔符
- //省略nameFilter和valueFilter过滤之后的输出处理
- getter.writeProperty(serializer, propertyValue);//使用字段解析器输出内容
- }
- out.append('}');//后缀
- }
由上可见,javaBean的输出实际上和map输出差不多。只不过这里又把属性的解析和输出封装了一层。在使用字段解析器(由FieldSerializer标识)输出字段值时,实际上也是先输出字段名+冒号,再输出字段值。这里就不再详细叙述。
总结
在整个解析过程中,更多的是根据对象类型查找到对象解析器,再使用对象解析器序列化对象的过程。在这中间,根据不同的对象采取不同的解析,并在实现中采取部分优化措施,以尽量地提高解析效率,减少中间运算。减少中间运算,是在解析过程中采取的最主要的优化办法。实际上,最主要的优化措施还是体现在outWriter中对于数据的处理上。此处更多的是设计模式的使用,以实现繁多的对象的解析。
整个fastjson的序列化部分,就到此为止。单就笔者而言,在查看源代码的时候,也发现了一些问题,可能是作者未考虑的问题,或者是实际中未遇到。但在版本升级过程中,也渐渐地对功能进行了增强,比如对于@JsonField注解的使用,NameFilter和ValueFilter的使用,使fastjson越来越符合业务系统的需要。如果可以,笔者会将其用到笔者所在的项目中,而不再重复发明轮子:)