Fastjson源码浅析

背景

最近在准备对项目的混淆规则进行治理,毕竟线上项目没混淆,说出来多少有点丢脸。问题总得要有人解决,想从静态分析和动态分析两个方面来进行反射类的收集,这个通过 ASM 比较容易收集。但是后续的维护也是要做的,混淆维护的重灾区就是 FastJson 的使用,所以想看看有没有切入点,解决这个问题,顺便看看天天用的 FastJson具体怎么运行。

Fastjson版本:1.1.70.andriod

从字符串到对象

目标类型为JSONObject

以如下的调用为例:

javascript 复制代码
JSON.parseObject("{"lastName":"maomao","firstName":"wang"}");

从字符串解析为 Object 时,都最终会调用 com.alibaba.fastjson.JSON#parse(java.lang.String, int)

再看看,解析器 DefaultJSONParser :

那么当前 token 就是 LBRACE

再看 DefaultJSONParser#parse(java.lang.Object):

解析出 key,这里的 key 是一个 String 类型

当 key 解析后,继续解析 value :

此时的 ch=" ; 解析 value逻辑如下,获取字符串:

并在判定解析结束后,解析结果放入 object 这个JsonObject 对象中,而 parse()函数就是解析的核心入口。

目标类型为指定Class类型

以如下调用为例:

ini 复制代码
DemoBean demoBean = JSON.parseObject("{"lastName":"maomao","firstName":"wang"}", DemoBean.class);

当目标对象为指定Class类型时,最终会在JSON.parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features)

函数出收敛;

这里DefaultJsonParser比较熟悉了就不重复介绍了,需要主要关注它的parseObject方法:

这个方法里面,config 一般是 ParserConfig.getGlobalInstance()这个单利(这样可以避免解串器重复创建,同时采用了数组存储,保证速度的同时也能保证多线程下的正确性),需要关注的 ParseConfig#getDeserializerObjectDeserializer#deserialze这两个函数的调用。前者是创建或获取解串器 ( Deserializer ) 后者是解串器的具体解串( deserialze )逻辑。

ParseConfig#getDeserializer中从全局解串表中根据不同的目标类查找对应的解串器,如果不存在则创建:

putDeserializer将创建好解串器,放入全局的deserializers中,因此这里在运行时可以收集到所有被FastJson用作解析字符串的类,需要继续看JavaBeanDeserializer的具体实现;

JavaBeanDeserializer#

这里为了方便理解,JavaBeanInfo#build 功能重点解释一下,这个方法比较长,代码就不粘贴,其主要功能如下:

通过反射将clazz的每个成员变量( field )与其所在的class、setter方法及注解信息关联(当有"is"开头的方法,则会去尝试关联对应的bool变量,当个 filed 为 Map 或者 Collection时则会去尝试关联对应的"get"------因为Map 或者 Collection 的解析是先创建一个JSONObject实例,然后解析过程中往JSONObject中添加元素,所有会用到"get"方法)并封装成一个FieldInfo、构造方法信息存储在这个Bean内,供后续解析使用。

并且可以看到,在 JavaBeanDeserializer初始化时,会给每个 FieldInfo 对象一个再分配一个 FieldDeserializer( 内部就是代理了ObjectDeserializer),这样便可以逐层解析了。

JavaBeanDeserializer#deserialze

再看看它的 deserialze 函数:

通过词法分析器 JSONLexer 将 String 解析,再利用上一步的 Bean 类的信息发射调用setter或直接反射赋值成员变量,进行赋值。

这里会根据 key 的名称与 field 的名称(或@JSONField携带的 name)匹配,所以混淆时有两种选择,field 不混淆或在 field 上注解 @JSONField 并附上 name 属性,否则解析时会匹配不上。

另外再看一下,field的设置过程fieldDeser.setValue(object, fieldValue);:

这里的method就是,上面讲到的 JavaBeanDeserializer初始化过程中收集的;

小结:Fastjson 的 parse 过程,大致可以描述成使用词法分析器 ( JSONLexer )将字符串拆成key-value形式,然后引导解串器 ( JavaBeanDeserializer) 对value字符串进行解析,解析过程中通过反射创建具体Bean实例并完成变量赋值。

从对象到String

以如下代码为例:

ini 复制代码
DemoBean demoBean = JSON.parseObject("{"lastName":"maomao","firstName":"wang"}", DemoBean.class);
String s = JSON.toJSONString(demoBean);//从对象到String

将对象转为String的逻辑稍微简单些,从对象到 String 最终会收敛到 JSON#toJSONString(Object object, SerializeConfig config, SerializeFilter[] filters, String dateFormat, int defaultFeatures, SerializerFeature... features),所以接下来看看这是函数实现。

JavaBeanDeserializer#

这里 config 变量一般是全局单例SerializeConfig.globalInstanceSerializeWriter负责写缓存,内部使用 TheadLocal 保证缓存buffer的线程安全。我们主要关注ObjectSerializer#write实现:

这里与字符串解析中的ParseConfig#getDeserializer对应,这里是查找或创建ObjectSerializer对对象进行序列化;

从上述代码得知,最终是创建了JavaBeanSerializer来完成序列化工作,再看看看JavaBeanSerializer

内部逻辑与JavaBeanDeserializer相似,只是由setter方法的收集变为getter方法的收集,并为每个FileldInfo分配一个FieldSerializer,其内部实现基本由JavaBeanDeserializer代理。

JavaBeanDeserializer#write

JavaBeanSerializerwite 方法则是遍历上述 FileldInfo将对应的名称及 Value写入字符串。

小结

本文只是对 Fastjson 流程做了简单分析,实现结构清晰,内部实现较复杂。但是,从整个 Android 研发交付流程角度看,Fastjson 有点确实有需要改进的点。大多情况下,研发和测试使用的都是 Debug 包,Debug包是没有混淆的,所以混淆导致不能解析的事情,只会发生在 Release 包中,这时一般都到了交付的偏后阶段。要排查混淆的问题,成本也是有的,所以理应把问题暴露在自测阶段(不知道 fastjson 2+ 有没有考虑这个问题,但是他们要的是 fast,不考虑的概率比较大)。不是说应该抄袭Serializable,但加个标识接口,添加标识接口的混淆规则,并在运行期检测一下,也不是不行,当然在Debug期间 ASM 自己在JavaBeanDeserializerJavaBeanSerializer注册的地方加一下,应该就能很好地解决 Fastjson 这个问题了。

相关推荐
m0_748235954 小时前
CentOS 7使用RPM安装MySQL
android·mysql·centos
AI航海家(Ethan)6 小时前
PostgreSQL数据库的运行机制和架构体系
数据库·postgresql·架构
贾贾20237 小时前
配电自动化系统“三区四层”数字化架构
运维·科技·架构·自动化·能源·制造·智能硬件
ac-er88887 小时前
Yii框架中的队列:如何实现异步操作
android·开发语言·php
流氓也是种气质 _Cookie9 小时前
uniapp 在线更新应用
android·uniapp
zhangphil11 小时前
Android ValueAnimator ImageView animate() rotation,Kotlin
android·kotlin
徊忆羽菲12 小时前
CentOS7使用源码安装PHP8教程整理
android
编程、小哥哥13 小时前
python操作mysql
android·python
lozhyf13 小时前
基于 JFinal 的国产微服务框架
微服务·云原生·架构
Couvrir洪荒猛兽13 小时前
Android实训十 数据存储和访问
android