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 这个问题了。

相关推荐
服装学院的IT男19 分钟前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms20622 分钟前
android 全面屏最底部栏沉浸式
android
服装学院的IT男25 分钟前
【Android 源码分析】Activity生命周期之onStop-1
android
ChinaDragonDreamer3 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
韩楚风5 小时前
【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
linux·服务器·性能优化·架构·gnu
网络研究院5 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下5 小时前
android navigation 用法详细使用
android
小比卡丘8 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭9 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android