概述

fastJson的功能是将json字符串转换成一个对象。

转换对象支持用@type转换成指定类型的对象。

既然有创建对象,必然涉及到类加载、newInstance、属性赋值。

本文记录了我如何分析fj查找对象属性并赋值和调用getter的过程。

分析思路

若直接从入口一步步调试,工作量太大,容易跟不到关键点。

所以先把断点打在sink,直接运行到sink点,此时调用栈显示出来的全都是关键节点,对着这些分析即可。

查看调用栈

版本1.2.22。

byte[] bytes0 = SmallShell.rceHorseCmd("calc");
bytes0 = "asd".getBytes();
ParserConfig config = new ParserConfig();

String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\""+ Base64.getEncoder().encodeToString(bytes0) +"\"],\"_name\":\"a.b\",\"_tfactory\":{ },\"_outputProperties\":{ }}";

Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);

断点先打在getOutputProperties,分析栈。

image-20240229104032099.png

去到FieldDeserializer#setValue

Method method = fieldInfo.method;
...
else if (Map.class.isAssignableFrom(method.getReturnType())) {
Map map = (Map) method.invoke(object);
}

fieldInfo信息:

image-20240229104502147.png

所以现在要知道:

1、fieldinfo如何获取到对象的属性的,且他获取的是outputProperties,实际上应该是_outputProperties

2、如何获取到getOutputProperties的

所以应该在调用栈找到最开始出现fieldinfo的地方,在那附近寻找。

获取对象属性

第一处

来到JavaBeanDeserilizer#deserialze,这里也存在fieldinfo。

image-20240229105012338.png

但是再下一层就不存在了,所以应该从这里开始看。

image-20240229105137929.png

断点打在这,然后一直跟进

image-20240229105509773.png

一直来到JavaBeanInfo#build

image-20240229105534325.png

这里面再细看一下,这就是获取上面提到的filedinfo的地方。

JavaBeanInfo#build内容概括:

1、反射获取所有方法

2、遍历所有方法,获取setter。获取满足规则的setter,必须以set开头,参数只能有1个等等,还有别的规则。

3、对setter处理,若set后面第一个字符是大写,则fieldname就是set后面的单词。

4、setter及其fieldname组成一个FieldInfo,存入JavaBeanInfo对象的fields属性。

5、第二次遍历所有方法,获取getter,getter的返回值得满足:

Collection.class.isAssignableFrom(method.getReturnType()) //
|| Map.class.isAssignableFrom(method.getReturnType()) //
|| AtomicBoolean.class == method.getReturnType() //
|| AtomicInteger.class == method.getReturnType() //
|| AtomicLong.class == method.getReturnType() //

6、getter方法的JSONField注解的name值是fieldname,若没有注解,则fieldname就是get后面的单词,首字母转小写。

最终返回的beaninfo。

这也解释了为什么TemplatesImpl属性名是_outputProperties但获取的是outputProperties

image-20240228230353939.png

此时猜测:

属性赋值就是使用FiledInfo里的name和setter来赋值的。但为什么有getter还不清楚。

此时还有两个问题:

1、这里并没有_bytecodes的FieldInfo,说明还有别的地方获取。

2、这里outputProperties的FieldInfo里的name是outputProperties,没有下划线,但JSON字符串里的有下划线,两者是如何匹配的?

第二处

首先看第一个。

断点打在这,此时key是_bytecodes

image-20240229102930354.png

跟进parseField一次即可,然后里面的内容概括:

0、这个方法的作用是:获取fieldDeserializer,然后调用fieldDeserializer#parseField,给对象属性赋值。下面是获取fieldDeserializer的过程。

1、执行fieldDeserializer = smartMatch(key); 若返回不是null,则return,否则继续。(这个smartMatch后面还会用到)。

2、反射获取所有属性,根据名字匹配,若匹配到,则将name和Filed对象组成FieldInfo对象,然后生成Deserializeer

image-20240229111648973.png

再然后就是赋值操作,由于fieldInfo的method为null,就直接反射赋值了。

此时对比最上面的outputProperties,method不是null,但field属性为null,直接调用的method。

第一个问题解决了,看第二个。

属性匹配

断点打在smartMatch,令key为_outputProperties,然后一直跟进。

image-20240229112310425.png

smartMatch方法内容概括:

1、首先在this已有的sortedFieldDeserializers里根据key二分查找deserializer。

2、若找不到,则检查key是否以下划线开头,若是,则去掉下划线,再找一次。

所以,_outPutProperties去掉下划线之后就成功找到了deserializer。

总结

1、fj先通过类的setter和getter获取fieldName和method。fieldName和method组成FieldInfo。

2、在将json字符串里的key转换成对象field的过程中,先去第一步获取到的FieldInfo中查找,若没有,则使用反射进行第二次查找field。同样组成FieldInfo。这里的FieldInfo包含了FieldInfo和Field,method为null。

3、在第二次查找属性的过程中,若key以下划线开头,则会去掉下划线。

4、当属性查找完成,则使用setValue。这里会使用第1、2步获取的FieldInfo,若method为null,则用反射赋值。若不为null,无论是getter还是setter,都会调用method.invoke(obj,Field)

5、创建对象,应该只需要setter,为何还要getter,这里不理解。