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

相关推荐
C4rpeDime2 小时前
自建MD5解密平台-续
android
盛派网络小助手2 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
鲤籽鲲3 小时前
C# Random 随机数 全面解析
android·java·c#
快乐非自愿7 小时前
分布式系统架构2:服务发现
架构·服务发现
2401_854391087 小时前
SSM 架构中 JAVA 网络直播带货查询系统设计与 JSP 有效实现方法
java·开发语言·架构
264玫瑰资源库7 小时前
从零开始C++棋牌游戏开发之第二篇:初识 C++ 游戏开发的基本架构
开发语言·c++·架构
神一样的老师7 小时前
面向高精度网络的时间同步安全管理架构
网络·安全·架构
2401_857026237 小时前
基于 SSM 架构的 JAVA 网络直播带货查询系统设计与 JSP 实践成果
java·开发语言·架构
9527华安7 小时前
FPGA实现MIPI转FPD-Link车载同轴视频传输方案,基于IMX327+FPD953架构,提供工程源码和技术支持
fpga开发·架构·mipi·imx327·fpd-link·fpd953
DT辰白7 小时前
如何解决基于 Redis 的网关鉴权导致的 RESTful API 拦截问题?
后端·微服务·架构