依赖
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency> </dependencies>
|
知识准备
如果正常情况下,FastJson是如何被使用的呢?
参数获取和参数解析
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.natro92;
import com.alibaba.fastjson.JSONObject;
public class FastJsonNormalTest { public static void main(String[] args) { String jsonDemo = "{\"name\":\"Boogipop\",\"age\":\"Hacker\"}"; JSONObject jsonObject = JSONObject.parseObject(jsonDemo); System.out.println(jsonObject); System.out.println("I am " + jsonObject.getString("name") + ", and I am a " + jsonObject.getString("age") + "!"); } }
|
对象解析
将Json格式解析为对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.natro92;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject;
public class FastJsonNormalTest {
public static void main(String[] args) { String jsonDemo = "{\"name\":\"Boogipop\",\"age\":\"Hacker\"}"; Hacker hacker = JSON.parseObject(jsonDemo, Hacker.class); System.out.println(hacker.getName()); } }
class Hacker { private String name; private String age; public Hacker() { System.out.println("Hacker constructor"); }
public Hacker(String name, String age) { this.name = name; this.age = age; }
public String getName() { System.out.println("Hacker detect getName: " + name); return name; }
public void setName(String name) { System.out.println("Hacker detect setName: " + name); this.name = name; }
public String getAge() { System.out.println("Hacker detect getAge: " + age); return age; }
public void setAge(String age) { System.out.println("Hacker detect setAge: " + age); this.age = age; } }
|
我们能注意,这里调用了get set方法
使用type关键字序列化任意类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package com.natro92;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject;
public class FastJsonNormalTest {
public static void main(String[] args) { String jsonDemo = "{\"@type\":\"com.natro92.Hacker\",\"name\":\"Boogipop\",\"age\":\"Hacker\"}"; JSONObject jsonObject = JSON.parseObject(jsonDemo); System.out.println(jsonObject); } }
class Hacker { private String name; private String age; public Hacker() { System.out.println("Hacker constructor"); }
public Hacker(String name, String age) { this.name = name; this.age = age; }
public String getName() { System.out.println("Hacker detect getName: " + name); return name; }
public void setName(String name) { System.out.println("Hacker detect setName: " + name); this.name = name; }
public String getAge() { System.out.println("Hacker detect getAge: " + age); return age; }
public void setAge(String age) { System.out.println("Hacker detect setAge: " + age); this.age = age; } }
|
在实例化的过程调用了get、set方法来修改值。
流程分析
调试
在JSON.parseObject(jsonDemo)
断点。
进入
再进
进
这里的Parse.parse
进去能发现,其中调用的parseObject
方法,
里面对key判断是否是需要进行java
反序列化或者是json
反序列化,因为匹配到了关键字@type
,所以进行java反序列化。
然后调用类加载loadClass
。
构建反序列化器,然后反序列化,进入getDeserializer
再进getDeserializer
这里提前内置了一些反序列化类,但是自己写类的肯定没有。
步过。
这里解析了JsonType注解。
继续过,能注意到这里有个黑名单,里面是线程,可能就会出现一些问题。
然后就是匹配是否是一些类,比如time
类
再往下,检测是否是枚举或者数组等这些数据类型。
这里如果都不是,那么就会默认创建一个JavaBean
来处理:
进去。
asmEnable
是用来动态创建动态加载。
然后判断一下获取什么参数之类的。如果满足就改成false
但是这里出了个问题,我这里会在这个getTypeParameters
方法中长度不为零,导致asmEnable赋值false,无法继续下去。
因此可以更改格式,把Hacker类独立成一个java文件,然后重新调试这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.natro92;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject;
public class FastJsonNormalTest {
public static void main(String[] args) { String jsonDemo = "{\"@type\":\"com.natro92.Hacker\",\"name\":\"Boogipop\",\"age\":\"Hacker\"}"; JSONObject jsonObject = JSON.parseObject(jsonDemo); System.out.println(jsonObject); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.natro92;
public class Hacker { private String name; private String age; public Hacker() { System.out.println("Hacker constructor"); }
public Hacker(String name, String age) { this.name = name; this.age = age; }
public String getName() { System.out.println("Hacker detect getName: " + name); return name; }
public void setName(String name) { System.out.println("Hacker detect setName: " + name); this.name = name; }
public String getAge() { System.out.println("Hacker detect getAge: " + age); return age; }
public void setAge(String age) { System.out.println("Hacker detect setAge: " + age); this.age = age; } }
|
这样就可以了:
继续,进入这个JavaBeanInfo
的build
方法。
我们可以看调试的这些变量,methods
获取默认构造方法,declaredFields
获取变量
默认方法不仅有get
、set
这些还有一些Exception
类,
然后构造器:
然后继续过
允许访问构造函数,下面这里比较重要:
遍历方法,获取你的方法名称字段,然后判断长度、判断是不是static
、不是返回空这些都跳过。
这里我也懵逼了,看了视频我才明白。这里遍历三次,第一次遍历方法,第二次获取字段,第三次又遍历一次方法。
这里的意思是第一次找所有的setter
,第二次要找所有的静态方法,第三次要找getter
setter
set的长度不会是三个了,一般的返回值是void,这里就解释通了
其实如果看了后面这里就很明白了:
这里直接判断是不是set
开头的方法。
继续下去,
名称处理,将set后的大写字符转换为小写字符,比如这里的setAge
的A
转换为小写
下面还有下划线等文本处理,如果没找到,就寻找is开头的方法名:
提高对多种命名的判断。
然后继续下去
创建FieldInfo
,进入
统一名称,如果不一样统一。然后一直过下去。
这里没法进到else里面,一路过下去,都进不去。
就出add方法了。
这里没有static
方法,就跳过
然后下去到遍历getter方法:
前面的逻辑都类似,长度、静态方法等
如果返回格式是这几种而且没有setter、只有getter,就会进入、调用add方法。
但是我们的测试demo都不满足。
然后就到下面返回处:
返回创建的构造函数。
然后出去,我们就能看到beanInfo找到所有的字段
过下去
如果是getOnly、不是Public类、不是成员类等,就会把asmEnable关掉,这里就是前面不会进入到asmEnable的原因,前面就有一个类判断。
我们继续往下可以发现,如果这里asmEnable
是false,这里就会创建一个JavaBeanDeserializer
。
然后再build一次。
这里我们下到asmFactory.createJavaBeanDeserializer
这里,用asm创建一个反序列化器。
这里的临时反序列化器无法进行调试。
因此我们需要满足如果是getOnly
是true,这里就不会调用asmEnable
PS: 这里看了评论区其实有别的解法
关于绕过ASM动态生成类去调试的方法,直接关闭asm:
调试toJSONString:SerializeConfig.getGlobalInstance().setAsmEnable(false);
调试反序列化函数:ParserConfig.getGlobalInstance().setAsmEnable(false);
其中FieldInfo这个构造里面能改,需要让getParameterTypes
不唯一
但是这里只有get方法能进来不行。
这里看到的解决办法是添加一个Map类型的参数,且只有get方法的属性,比如:
1 2 3 4 5
| private Map map; public Map getMap() { System.out.println("Hacker detect getMap: " + map); return map; }
|
如果有set方法就不会存到get方法里面了。
重新断点,回到遍历setter、getter方法那里(JavabeanInfo
类下的328行开始):
直接跳到遍历getter(490行):
进去方法到返回类型判断
返回的是Map类型可以进来
然后到最后add进来
进一下FieldInfo
这里,找到我们需要的getOnly这里:
因为当前参数值是零,所以会进到下面的else。
getOnly就变成了true
。
然后就可以到ParserConfig
的对asmEnable
这里的判断。
进去
直接过,过到derializer
接到返回值
我们看下里面:
这里得到了几个字段。
然后调用反序列化。
过出去,现在就得到了反序列化器。
然后进入deserialize
反序列化方法。
一路进
往下:
遍历字段信息。一路下去
创建实例
如果是接口需要创建动态代理,如果不是调用构造函数。
赋值
一路过
在下面执行invoke
了
执行了set
那get在哪?get在Json.toJSON
方法这里
也就是我们最开始到的位置(又回到最初的起点)
也就是将json转成对象调用setter
,然后对象转成json又调用getter
我们直接跳转过来,果然没有执行。
一路进,然后进入getObjectWriter
往下来到buildBeanInfo
:
这里获取到fieldInfoList
,获取到参数,到这里时已经有method了。
一路过回到JSON
的
断下FieldInfo的get方法
在这里invoke执行的get方法。