fastjson反序列化漏洞1-流程分析

环境搭建

先生成这种结构的项目:

然后在pom.xml中添加依赖:

 <dependencies>
        <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.24</version>
        </dependency>
 
 
        <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.5.1</version>
        </dependency>
 
 
        <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.25</version>
        <scope>test</scope>
        </dependency>
 
 
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.25</version>
        </dependency>
 
 
    </dependencies>

然后就增加了依赖:

基础知识

FastJson是Java语言编写的高性能功能完善的JSON库,对象转换为JSON格式
漏洞主要围绕着setter、getter方法

漏洞版本

1.2.24版本以下,漏洞产生的原因主要是没有对序列化
1.2.25-1.2.41 版本增加了黑名单限制
1.2.68 使用类AutoCloseable来绕过fastjson校验

参考

https://www.bilibili.com/video/BV1bD4y117Qh?t=7.0

示例代码

package org.example.demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class JSONUser {
    public static void main(String[] args) throws Exception {
        String s = "{\"param1\":\"aaa\",\"param2\":\"bbb\"}";
        JSONObject jsonObject = JSON.parseObject(s);
        System.out.println(jsonObject);
        System.out.println(jsonObject.getString("param1"));
    }
}

fastjson的作用就是将字符串转换为JSON,然后可以进行JSON操作

如:

String s = "{\"age\":18,\"name\":\"abc\"}";
Person person = JSON.parseObject(s,Person.class); // 直接解析为一个Person对象
System.out.println(person.getName());

实际上是调用了construtor -> setAge ->setName -> getName -> abc

另一个示例:

// Person.java
package org.example.demo;

import java.util.Map;

public class Person {
    private Integer age;
    private String name;
    private Map map;
    public Person() {
        System.out.println("constructor");
    }

    public Person(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    public Integer getAge() {
        System.out.println("getAge");
        return age;
    }

    public void setAge(Integer age) {
        System.out.println("setAge");
        this.age = age;
    }

    public String getName() {
        System.out.println("getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }

    public Map getMap(){ // 有set就没get,所以这里不能有setMap
        return map;
    }
}

// fastjson.java
package org.example.demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class JSONUser {
    public static void main(String[] args) throws Exception {
//        String s = "{\"param1\":\"aaa\",\"param2\":\"bbb\"}";
//        String s = "{\"age\":18,\"name\":\"abc\"}";
//        String s = "{\"@type\":\"org.example.demo.Person\",\"age\":18,\"name\":\"ttt\"}";
        String s = "{\"@type\":\"org.example.demo.Test\",\"cmd\":\"calc\"}";
        JSONObject jsonObject = JSON.parseObject(s);
        System.out.println(jsonObject);

    }
}

@type指定为Person类,然后
按照Person类进行解析、实例化、赋值、调用,全做了一遍
这个constructor是空参构造函数里面的
而这个行为不是代码端控制的,而是客户端控制的,会根据传入的字符串的不同,然后找不同的代码路径

可能类似于一句话木马,传入不同的字符串,执行不同的代码

流程分析

第一次调试


跟进:

发现进入接收String类型的parseObject

public static JSONObject parseObject(String text) {
    Object obj = parse(text);
    if (obj instanceof JSONObject) {
        return (JSONObject) obj;
    }

    return (JSONObject) JSON.toJSON(obj);
}

这个parseObject方法的作用是解析传入的字符串text,将其转换为一个JSONObject对象
关于这个JSONObject:

实现了map

继续跟进parse方法:

跟进:

看到这里使用了DefaultJSONParser方法
字符串传进去后会调用DefaultJSONParser进行解析

DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);

ParserConfig.getGlobalInstance() 指ParserConfig的实例化对象
后面有个features,代表着解析时的规则要求,如能不能用单引号,传入了空格怎么处理

继续跟进:

步入,来到了DefaultJSONParser类的parse方法:

继续跟进:

final JSONLexer lexer = this.lexer;
switch (lexer.token()) {
    case SET:
        lexer.nextToken();
        HashSet<Object> set = new HashSet<Object>();
        parseArray(set, fieldName);
        return set;
    case TREE_SET:
        lexer.nextToken();
        TreeSet<Object> treeSet = new TreeSet<Object>();
        parseArray(treeSet, fieldName);
        return treeSet;
    case LBRACKET:
        JSONArray array = new JSONArray();
        parseArray(array, fieldName);
        if (lexer.isEnabled(Feature.UseObjectArray)) {
            return array.toArray();
        }
        return array;

首先声明了一个名为lexer的JSONLexer对象,他是当前对象的成员变量this.lexer的引用
lexer.token()获取当前令牌,并根据这个令牌的值执行不同的分支
现在就是字符串挨个去匹配

case LBRACKET 为左中括号
case LBRACE 为左大括号


现在的第一个字符就是左大括号,所以进入了CASE LBRACE

case LBRACE:
    JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
    return parseObject(object, fieldName);

这个分支处理JSON对象,它创建一个JSONObject对象,并调用parseObject方法来填充对象,并返回填充好的对象

跟进这个parseObject方法:

这个parseObject方法还挺长的。。。。
它根据不同的JSON数据类型来解析JSON字符串,并返回相应的Java map对象,这个方法依赖于JSONLexer类来逐个读取JSON字符串中的令牌,并依赖于parseArray和parseObject方法来递归解析数组和对象

对边界值的判断:


这里新建了一个key,之后就要解析这个key了

key = lexer.scanSymbol(symbolTable, '"');

这一行把双引号之间的值读出来,即@type

读到key之后:

进行匹配

JSON.DEFAULT_TYPE_KEY

指的是@type
匹配到这个@type的话说明做的事是Java的反序列化,而不只是json的

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
      String typeName = lexer.scanSymbol(symbolTable, '"');
      Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());

使用了loadClass加载这个类
跟进这个loadClass查看:
来到了TypeUtils类的loadClass

Class<?> clazz = mappings.get(className);

指先从内存中查找,如果有的话直接获取,相当于空间换时间


A处指如果匹配到第一位是[ ,则说明是数组,接下来用数组的方式处理
B处指如果以L开头和以;结尾,则直接删除这两个字符再loadClass加载

继续跟进:

if (contextClassLoader != null) {
    clazz = contextClassLoader.loadClass(className);
    mappings.put(className, clazz); // 指放入缓存里面

    return clazz;
}

这里获得线程上下文的类加载器 Launcher$AppClassLoader
然后加载传入的org.example.demo.Person类,并放入缓存里面

继续跟进:

这个object就是指传进来的空的键值对,每加载完一轮key-value之后就会向这个object里面放

继续跟进,来到这里:

ObjectDeserializer deserializer = config.getDeserializer(clazz);
  return deserializer.deserialze(this, clazz, fieldName);

之前使用它的解析器去解析,是基于字符串的操作,都是解析JSON字符串。
接下来就是解析Java的方法去反序列化(也可以叫做解析JAVA对象)
先通过config.getDeserializer获得反序列化器,然后利用反序列化器去反序列化,这步做完返回的就是Person了

接着跟进这个反序列化器getDeserializer
来到ParserConfig类的getDeserializer方法:

ObjectDeserializer derializer = this.derializers.get(type);

this.deserializers为缓存,这也是从缓存表里去找

继续跟进,来到getDeserializer类:

跟进getDeserializer类:

继续跟进:

这里

JSONType annotation = clazz.getAnnotation(JSONType.class);

在获得JSONType注解,如果自己写了这个的话相当于写了个反序列化器

继续跟进:

这个denyList是一个黑名单,更多的是考虑性能


这里进行了匹配,为java.awt包中的特定类注册一个自定义的反序列化处理器

之后也是进行类似的匹配,并注册类似的反序列化器

clazz.isEnum()

判断clazz是否是一个枚举类型
然后接着挨个匹配

derializer = createJavaBeanDeserializer(clazz, type);

如果上面都不是,则调用createJavaBeanDeserializer,按照JavaBean解析

跟进createJavaBeanDeserializer方法:

public ObjectDeserializer createJavaBeanDeserializer(Class<?> clazz, Type type) {
  boolean asmEnable = this.asmEnable;
  if (asmEnable) {
      JSONType jsonType = clazz.getAnnotation(JSONType.class);

      if (jsonType != null) {
          Class<?> deserializerClass = jsonType.deserializer();
          if (deserializerClass != Void.class) {
              try {
                  Object deseralizer = deserializerClass.newInstance();
                  if (deseralizer instanceof ObjectDeserializer) {
                      return (ObjectDeserializer) deseralizer;
                  }
              } catch (Throwable e) {
                  // skip
              }
          }
          
          asmEnable = jsonType.asm();
      }

其中,

boolean asmEnable = this.asmEnable;

用于从当前对象中获取ASM是否启用的状态,默认为true

asmEnable在某些情况下是不支持的
比如跟进到这里:

if (!Modifier.isPublic(superClass.getModifiers())) {
      asmEnable = false;
      break;
  }
Modifier.isPublic(superClass.getModifiers())

用于检测一个类superClass是否是public修饰,如果不是的话就使得asmEnable=false,导致加载不了

继续跟进:

JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy);

这个build方法用于创建类对应的反序列化器时,他要把类里面的东西先了解清楚,如构造函数、Getter、Setter,然后把它组成JavaBeaninfo
跟进build:

public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy)

注意用的是JavaBeanInfo这个类
这个函数的实现很重要

第134行:

Field[] declaredFields = clazz.getDeclaredFields();
Method[] methods = clazz.getMethods();

获取所有的字段:

获取所有的方法:
image-20241016100754593
包括Getter、Setter

get

跟进到这里:

for (Method method : methods)

这段在遍历method,目的是获取所有字段

这里的method还包括继承的Object里面的方法
从整体上看:

for (Method method : methods)
for (Field field : clazz.getFields())
for (Method method : clazz.getMethods())

第一次遍历method是找所有的Setter:

image-20241016100826539

if (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
      continue;
  }

这里进行了if判断,第一个条件是返回值是void修饰,第二个条件是用于检测一个方法是否返回其自身类型的实例
然后当method为setName的时候:

将前三个字符set设为小写,加上截取的后面的内容
继续跟进:
image-20241016100847106

Field field = TypeUtils.getField(clazz, propertyName, declaredFields);

跟进这个getField:

从所有的field里面找,随机匹配

继续跟进:
image-20241016100906245
找到之后就要创建FieldInfo
跟进这个FieldInfo:

继续跟进,来到getOnly这里:
image-20241016100921211
这个getOnly在之后可能会遇到

关于这儿的add方法:

这个add的作用就是往一个List里面放

第二个遍历所有public变量:

for (Field field : clazz.getFields())

如果没有public修饰的成员变量的话,就会跳过

第三个遍历method是找所有的Getter:

for (Method method : clazz.getMethods()) { // getter methods
  String methodName = method.getName();
  if (methodName.length() < 4) {
      continue;
  }

跟进到这里:

if (Collection.class.isAssignableFrom(method.getReturnType()) //
    || Map.class.isAssignableFrom(method.getReturnType()) //
    || AtomicBoolean.class == method.getReturnType() //
    || AtomicInteger.class == method.getReturnType() //
    || AtomicLong.class == method.getReturnType() //
) 

只有返回值是Map、AtomicBoolean、AtomicInteger、AtomicLong的时候,才会进入:
image-20241016100947941
就是只有setter,没有getter,才会把它作为单独的方法加入add()
显然不满足,于是进入:

进入JavaBeanInfo的构造方法

继续跟进:

通过刚才的Getter、Setter方法得到两个字段--name和age,同时也得到这些字段对应的setter方法,但是没有getter方法,因为有setter就不会去找gettter,因为反序列化讲究的是赋值,即setter

获取完后还有机会把asmEnable关掉

这个asmEnable有什么用呢?

就是在这里:(继续跟进)

if (!asmEnable) {
    return new JavaBeanDeserializer(this, clazz, type);
}

如果asmEnable为false的话,就直接创建一个JavaBeanDeserializer

JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy);

如果为true的话,就再build一次

return asmFactory.createJavaBeanDeserializer(this, beanInfo);

用的是asmFactory,临时创建一个类进行反序列化
跟进去:

这个会有什么问题呢?
这是一个临时创建的类,我们调试不了
image-20241016101017666
JavaBeanDeserializer是它的父类,我们只能知道它有操作,但是不知道具体干了什么

所以我们要让asmEnable = false
选用的方法是:

让这里的getOnly为true

全局查找getOnly的用法:
image-20241016101035244
现在想的是怎么进到getOnly = true这里

即:

if ((types = method.getParameterTypes()).length == 1)

这行代码获取当前处理的方法的所有参数类型,并将其赋值给变量types
检查方法是否恰好有一个参数,如果是,则执行if块内的代码
显然,我们的目的是进入else块内的代码,所以要让它的参数类型不为1

但是默认来说,进到这里面的,它的流程是把所有满足条件的set方法放进来,但是其中有个条件是:

即长度不为1的话就退出了

所以如果是set方法的话,就不能使得getOnly为true

其中从名字也可以看出,不能有set方法,只能有get方法

如果想要从gettter进来的话,就要让返回值满足这几种:

即:

Map
AtomicBoolean
AtomicInteger
AtomicLong

所以在Person类增加一个属性的get方法,这个方法的返回值是上面四种之一,而且这个属性只有get方法,没有set方法,因为有setter就没有getter
即增加:

import java.util.Map;
private Map map;
public Map getMap(){
    return map;
}

即把Person类变为:

package org.example.demo;

import java.util.Map;

public class Person {
    private Integer age;
    private String name;
    private Map map;
    public Person() {
        System.out.println("constructor");
    }

    public Person(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    public Integer getAge() {
        System.out.println("getAge");
        return age;
    }

    public void setAge(Integer age) {
        System.out.println("setAge");
        this.age = age;
    }

    public String getName() {
        System.out.println("getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }

    public Map getMap(){ // 有set就没get,所以这里不能有setMap
        return map;
    }
}

第二次调试

跟进build,然后一直步过
来到三部分:

三部分:第一个遍历所有settter,第二个遍历所有public字段,第三个遍历所有getter

第一个跑完放了两个setter方法,直接跳到第一个部分的add方法这儿来:

这里循环add两次,第一次是setName,第二次是setAge
来到:

for (Field field : clazz.getFields()) { 

由于没有public字段,所以直接跳到第三部分遍历getter方法:

然后又开始循环,直到getMap方法:

继续:

发现:

Map.class.isAssignableFrom(method.getReturnType())

这里的值为true
所以进入这一部分:

然后来到add方法:

步入FieldInfo的构造方法,然后跳到这儿:

由于getMap的参数值是空,即0,所以进入else块:

这里就让getOnly为true了
这样就有一个getOnly = true的 field

接着步出刚才的build:

跳到for循环的这里:

多次循环后到了map,它的getOnly = true:

使得fieldInfo.getOnly = true,进入if块:

这样使得asmEnable = false

接着跳到:

进入了:

return new JavaBeanDeserializer(this, clazz, type);

接着步入到JavaBeanDeserializer里面开始调试:

强制运行到断点这里:

发现deserializer(反序列化构造器)变为了JavaBeanDeserializer
接着步入开始反序列化:

继续步入,来到了这里:

现在捋一下目前做了什么

第一步是parse:

和parseObject:

先按着字符串解析,并且发现有@type的话就按Java对象解析
重点在这里:

对于这个类,我们需要有反序列化器,刚才所做的都是为了把这个反序列化器拿到,然后调用对应的反序列化方法deserialze

关于刚才演示中调用了构造函数、Getter、Setter这件事
首先来到这里:

这个for循环是在遍历所有字段
可以通过查看当前fieldinfo信息获知:

如果有field对应的反序列化器:

接着跳到这儿:

跟进这个createInstance:

如果是接口的话就创造动态代理,如果不是接口的话,就调用构造方法
跟进到这儿:

此时控制台:

调用完构造函数之后就是给他赋值:

跟进这个setValue方法:

method.invoke(object, value);

这里调用invoke方法调用setValue方法

循环调用:

控制台:

然后map那个属性没有,因为在开始赋值的时候没有:

如果有的话,就会调用getMap方法
然后就结束了,来到了这儿:


obj是一个Person对象

那Getter方法在哪儿调用的呢?
实际上在这儿:

toJson把对象转为JSON,调用了Getter
跟进这个toJson,来到了getObjectWriter:

这个getObjectWriter对应之前的deserializer

跳到getFieldValueMap:

在getFieldValueMap里面调用了Getter

跟进getFieldValueMap

跳到getFieldValuesMap:

这个getFieldValuesMap里面调用了Getter

继续跳到这个for循环里面:

跟进getPropertyValue:

继续跟进:

跟进这个get方法:

Object value = method.invoke(javaObject, new Object[0]);

发现这里也是调用了invoke方法,从而调用Getter

跟进这个method.invoke:

这差不多就是整个流程了。。。

fastjson在解析的时候为什么会有代码执行的问题?
因为它一定会调用get和set方法,而且底层都是以invoke的方法调用的
即找一个恶意的set方法,然后把这个类传进去即可

即恶意类Test:

package org.example.demo;
import java.io.IOException;
public class Test {
    public void setCmd() throws IOException{
        Runtime.getRuntime().exec("calc");
    }
}

然后调用:

String s = "{\"@type\":\"org.example.demo.Test\",\"cmd\":\"calc\"}";
JSONObject jsonObject = JSON.parseObject(s);
System.out.println(jsonObject);
constructor 
setAge
setName
getAge
getName
{"name":"ttt","age":18}

即可

调试漏洞代码

以以下poc为例:
Test.java

package org.example.fastjson;

import java.io.IOException;

public class Test {
    public void setCmd(String cmd) throws IOException{
        Runtime.getRuntime().exec(cmd);
    }
}

fastjson_poc.java:

package org.example.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class fastjson_poc {
    public static void main(String[] args) throws Exception {
        String s = "{\"@type\":\"org.example.fastjson.Test\",\"cmd\":\"calc\"}";
        JSONObject jsonObject = JSON.parseObject(s);
        System.out.println(jsonObject);
    }
}

开始调试:

跟进JSON.parseObject方法:

这里有两个分支

Object obj = parse(text); // 调用Setter、构造函数


return (JSONObject) JSON.toJSON(obj); // 调用Getter

进入JSON.parse:

DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features); // 创建DefaultJSONParser对象
// 第二个参数为全局的ParseConfig实例
// features为解析器的特性
// 这相当于parser是一个解析器
Object value = parser.parse(); // 获得解析器的parse方法

跟进解析器里面:

public DefaultJSONParser(final String input, final ParserConfig config, int features){
    this(input, new JSONScanner(input, features), config);
    // this关键字这里用于引用当前对象的构造函数,称为this调用,用于在构造函数中调用同一个类中的另一个构造函数
}

public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){
    this.lexer = lexer;
    this.input = input;
    this.config = config;
    this.symbolTable = config.symbolTable;

    int ch = lexer.getCurrent();
    if (ch == '{') {
        lexer.next();
        ((JSONLexerBase) lexer).token = JSONToken.LBRACE;
    } else if (ch == '[') {
        lexer.next();
        ((JSONLexerBase) lexer).token = JSONToken.LBRACKET;
    } else {
        lexer.nextToken(); // prime the pump
    }
}

这里设置解析器的初始状态,并根据JSON文本的起始字符(对象或数组)设置相应token

接着进入DefaultJSONParser.parse方法:

case LBRACE: // 以{ 开头,说明是对象
    JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); // 获得JSONObject对象
    return parseObject(object, fieldName); // 解析对象

来到DefaultJSONParser.parseObject方法:

key = lexer.scanSymbol(symbolTable, '"');

这里把@type读取出来

有@type的话就按JAVA对象解析

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect))

Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());

ObjectDeserializer deserializer = this.config.getDeserializer(clazz);

第一部分的JSON.DEFAULT_TYPE_KEY:

@type指定为org.example.fastjson.Test类,然后按照org.example.fastjson.Test类去解析、实例化、赋值、调用。

第二部分的loadClass:

ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); // 获得线程上下文的类加载器Launcher$AppClassLoader
if (contextClassLoader != null) { 
    clazz = contextClassLoader.loadClass(className); // 获得Test类的Class对象
    mappings.put(className, clazz); // 将Test类名、Test的Class对象一起放到一个mappings映射中

    return clazz; // 返回Class对象

接着进入DefaultJSONParser.parseObject的这里:

ObjectDeserializer deserializer = this.config.getDeserializer(clazz);

这里把Test的Class对象传进去,最终获得反序列化器,即JavaBeanDeserializer类的实例化对象

接下来看怎么获得这个反序列化器的:
跟进ParserConfig.getDeserializer:

由于传入的是Test的Class对象,属于Class<?> 的实例化对象
接着进入另一个getDeserializer方法:

ObjectDeserializer derializer = derializers.get(type);

这里尝试从derializers的映射(Map)中获取与type对应的ObjectDeserializer实例,应该就是尝试获取Test.class的反序列化器

说了半天,反序列化器是什么?
反序列化器就是将数据格式(如JSON、XML)转换回原始的对象或数据结构。例如,将JSON字符串转换为Java对象。

查看derializers的定义:

derializers为IdentityHashMap的实例

继续跟到ParserConfig.getDeserializer的这里:

判断类名是否在黑名单中,防止潜在的安全危险,可能是为了保障线程安全

然后跟进到ParserConfig.getDeserializer的这里:

derializer = createJavaBeanDeserializer(clazz, type);

进行了各种判断,如枚举、数组等类型,显然我们创建的Test类不属于这里的任意一种类型
如果不是的话,则创建按照JavaBean解析的反序列化器,用于处理普通的JavaBean对象

跟进到ParserConfig.createJavaBeanDeserializer方法中:

boolean asmEnable = this.asmEnable;

asmEnable为true时代表启用了ASM(一种Java字节码操作框架),后面要用到

继续跟进到ParserConfig.createJavaBeanDeserializer的这里:

JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy);

为了创建Test类对应的反序列化器,这里调用JavaBeanInfo.build来把Test类里面的东西先了解清楚,如构造函数、Getter、Setter,然后把这些信息放到beanInfo里面

跟进到JavaBeanInfo.build里面:

Field[] declaredFields = clazz.getDeclaredFields(); // 获取所有的字段
Method[] methods = clazz.getMethods(); // 获取所有的方法
Constructor<?> defaultConstructor = getDefaultConstructor(builderClass == null ? clazz : builderClass); // 获取构造方法

随即就跟进到JavaBeanInfo.build的这三个for循环:

先看第一个

for (Method method : methods)

遍历所有的方法

我们的目的是不进入continue,即A处要求非静态(即不用static修饰),B处要求返回类型为void,C处要求有且只有一个参数


D处要求以set开头的方法名
E和F处看第四个字符是否是大写字母(JavaBean的格式要求)或者其ascii码大于512,如果不是,则先全部小写,再将第四个字符大写,使其满足JavaBean格式的方法的命名要求
然后到第一个for循环的add方法这里:

add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,annotation, fieldAnnotation, null));


将符合的放到fieldList里面

第一个for循环后fieldList列表的值:

只有一个cmd,因为有cmd的setter方法,所以将其放到fieldList之中

进入第二个for循环:

for (Field field : clazz.getFields())

第三个for循环:

显然就是将有getter方法的字段名放到FieldList之中

 return new JavaBeanInfo(clazz, builderClass, defaultConstructor, null, null, buildMethod, jsonType, fieldList);

将这个类的内部信息作为JavaBeanInfo的实例返回

接着步出JavaBeanInfo.build方法,来到这里:

if (fieldInfo.getOnly) {
    asmEnable = false;
    break;
}

这里fieldInfo.getOnly为false,不能进入if代码块,让asmEnable为false

继续跟进到这里:

步出PaserConfig.createJavaBeanDeserializer,得到Test类的反序列化器

接着步出ParserConfig.getDeserializer,来到DefaultJSONParser.parseObject这里:

return deserializer.deserialze(this, clazz, fieldName);

这里调用反序列化器中的deserialze方法进行反序列化

这个反序列化器就是JavaBeanDeserializer类的实例

进入JavaBeanDeserializer.deserialze方法:

继续跟进:
emmm,这里好像就跟进不了了。。

为什么跟进不了?因为在执行PersonConfig.getDeserializer的这里的时候:

return asmFactory.createJavaBeanDeserializer(this, beanInfo);

临时创建一个类进行反序列化,此时的反序列化器是:FastjsonASMDeserializer_1_Test类的实例:

由于这个类是临时创建的,项目中没有这个类,所以无法跟进调试

如果想要调试的话就要加上这一段代码:

private Map map;
public Map getMap(){
   return map;
}

加上这个之后反序列化器是JavaBeanDeserializer的实例:

接下来的调试就会进入JavaBeanDeserializer类

跟进到JavaBeanDeserializer.deserialze的这里:

跟进setValue内部:

method.invoke(object, value);

调用invoke方法

相当于执行:

object.setCmd("calc");

也就是调用了这个:

因此步过后就calc了

接着看怎么调用Getter方法的

在上面的基础上继续调试,来到JSON.parseObject的这里:

跟进,来到了JSON.toJSON的这里:

Map<String, Object> values = javaBeanSerializer.getFieldValuesMap(javaObject);

跟进getFieldValuesMap方法内部,来到javaBeanSerializer.getFieldValuesMap:

for (FieldSerializer getter : sortedGetters) {
    map.put(getter.fieldInfo.name, getter.getPropertyValue(object));
}

这段代码的目的是遍历sortedGetters结合中所有FieldSerializer对象,使用它们的getPropertyValue方法从传入的object中提取出每个字段的值

跟进getter.getPropertyValue,来到FieldSerializer.getPropertyValue内部:

跟进FieldInfo.get方法:

if (method != null) {
    Object value = method.invoke(javaObject, new Object[0]);
    return value;
}

发现这里也调用了method.invoke

method.invoke(javaObject, new Object[0]);

相当于执行:

test.getMap();

也就是在这里执行了Test类里的Getter方法
继续跟进来到这里:

这里就开始执行代码了。。

至此,这就是整个流程

如有误,请留言

posted @ 2024-10-16 10:22  starme  阅读(63)  评论(0)    收藏  举报