背景
最近在准备对项目的混淆规则进行治理,毕竟线上项目没混淆,说出来多少有点丢脸。问题总得要有人解决,想从静态分析和动态分析两个方面来进行反射类的收集,这个通过 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#getDeserializer
和 ObjectDeserializer#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.globalInstance
,SerializeWriter
负责写缓存,内部使用 TheadLocal
保证缓存buffer
的线程安全。我们主要关注ObjectSerializer#write
实现:
这里与字符串解析中的ParseConfig#getDeserializer对应,这里是查找或创建ObjectSerializer对对象进行序列化;
从上述代码得知,最终是创建了JavaBeanSerializer来完成序列化工作,再看看看JavaBeanSerializer
内部逻辑与JavaBeanDeserializer
相似,只是由setter
方法的收集变为getter
方法的收集,并为每个FileldInfo
分配一个FieldSerializer
,其内部实现基本由JavaBeanDeserializer
代理。
JavaBeanDeserializer#write
JavaBeanSerializer
的 wite
方法则是遍历上述 FileldInfo
将对应的名称及 Value
写入字符串。
小结
本文只是对 Fastjson 流程做了简单分析,实现结构清晰,内部实现较复杂。但是,从整个 Android 研发交付流程角度看,Fastjson 有点确实有需要改进的点。大多情况下,研发和测试使用的都是 Debug 包,Debug包是没有混淆的,所以混淆导致不能解析的事情,只会发生在 Release 包中,这时一般都到了交付的偏后阶段。要排查混淆的问题,成本也是有的,所以理应把问题暴露在自测阶段(不知道 fastjson 2+ 有没有考虑这个问题,但是他们要的是 fast,不考虑的概率比较大)。不是说应该抄袭Serializable
,但加个标识接口,添加标识接口的混淆规则,并在运行期检测一下,也不是不行,当然在Debug期间 ASM 自己在JavaBeanDeserializer
及JavaBeanSerializer
注册的地方加一下,应该就能很好地解决 Fastjson 这个问题了。