芥末
发布于 2025-10-15 / 0 阅读
0
0

Fastjson 1.2.83 序列化与反序列化源码机制解析

Fastjson 是阿里巴巴开源的 JSON(JavaScript Object Notation,一种轻量级数据交换格式)处理库,常用于 Java 对象和 JSON 字符串之间的转换。Fastjson 1.x 已经停止维护,新的项目更建议选择 Fastjson 2.x 或 Jackson,但 1.x 在不少存量 Java 系统中仍然存在,理解它的内部机制有助于排查序列化问题、反序列化异常以及 AutoType 安全风险。

序列化和反序列化本质上是在做“数据跨边界搬运”:

  • 序列化:把 Java 内存里的对象转换成 JSON 字符串,方便网络传输、落盘、写入缓存或跨语言调用。
  • 反序列化:把 JSON 字符串解析成 Java 对象,让业务代码可以继续用面向对象的方式处理数据。
flowchart LR
    A[Java 对象<br/>存在于 JVM 内存中] -->|序列化| B[JSON 字符串]
    B --> C[网络 / 磁盘 / 缓存 / 消息队列]
    C --> D[JSON 字符串]
    D -->|反序列化| E[Java 对象<br/>回到 JVM 内存中]

Fastjson 1.2.83 的核心不是简单地调用 getter、setter,而是围绕类型识别、序列化器缓存、字段扫描、字节码加速、上下文追踪和安全检查搭了一套完整流程。


1. 整体架构:从门面 API 到底层词法分析器

从使用者角度看,Fastjson 最常见的入口只有两个:

String json = JSON.toJSONString(object);

User user = JSON.parseObject(json, User.class);

但这两个静态方法背后会牵出多个核心模块。

flowchart TB
    API[用户接口层<br/>JSON / JSONObject / JSONArray]

    Config[配置管理层<br/>SerializeConfig / ParserConfig]
    Serializer[序列化引擎<br/>JSONSerializer / ObjectSerializer / SerializeWriter]
    Parser[反序列化引擎<br/>DefaultJSONParser / ObjectDeserializer / JSONLexer]
    Security[安全防护层<br/>AutoType / 黑白名单 / safeMode]
    Extension[扩展机制<br/>注解 / SPI / Module]

    API --> Config
    API --> Serializer
    API --> Parser
    Serializer --> Config
    Parser --> Config
    Parser --> Security
    Serializer --> Extension
    Parser --> Extension
层次主要职责代表类
用户接口层提供最常用的静态方法和 JSON 数据结构JSONJSONObjectJSONArray
配置管理层缓存类型到序列化器/反序列化器的映射,管理特性开关SerializeConfigParserConfig
序列化引擎把 Java 对象写成 JSON 字符串JSONSerializerObjectSerializerSerializeWriter
反序列化引擎把 JSON 字符串解析成 Java 对象DefaultJSONParserObjectDeserializerJSONLexer
安全防护层限制动态类型加载,降低反序列化攻击风险checkAutoTypesafeMode
扩展层支持自定义字段名、格式、序列化器和反序列化器@JSONField@JSONType、SPI、Module

2. 项目结构:核心包各管一块

Fastjson 1.x 的代码按职责拆得比较清楚,序列化、反序列化、注解、字节码工具和通用工具类分别放在不同包下。

com.alibaba.fastjson/
├── JSON.java                         # 核心入口类
├── JSONObject.java                   # JSON 对象结构,基于 Map
├── JSONArray.java                    # JSON 数组结构,基于 List
├── annotation/                       # 注解定义
│   ├── JSONField.java
│   └── JSONType.java
├── asm/                              # 精简 ASM 字节码工具
├── parser/                           # 反序列化模块
│   ├── DefaultJSONParser.java        # 默认 JSON 解析调度器
│   ├── JSONLexer.java                # 词法分析器接口
│   ├── JSONLexerBase.java            # 词法分析器基础实现
│   ├── JSONScanner.java              # 基于字符串的扫描器
│   └── deserializer/                 # 反序列化器
├── serializer/                       # 序列化模块
│   ├── JSONSerializer.java           # 序列化调度器
│   ├── SerializeConfig.java          # 序列化配置与缓存
│   ├── SerializeWriter.java          # 高性能 JSON 字符串写入器
│   ├── ObjectSerializer.java         # 序列化器接口
│   └── JavaBeanSerializer.java       # JavaBean 序列化器
├── spi/                              # SPI 扩展机制
├── support/                          # 框架适配支持
└── util/                             # 工具类

几个包的关系可以这样理解:

负责什么常见问题会落到哪里
com.alibaba.fastjson对外 API(Application Programming Interface,应用程序编程接口)和 JSON 容器toJSONStringparseObject 怎么进入内部流程
serializerJava 对象转 JSON字段为什么没输出、null 怎么处理、循环引用怎么写
parserJSON 转 Java 对象字段为什么没赋值、接口字段为什么还原失败、泛型为什么丢失
annotation声明式定制字段重命名、日期格式、自定义序列化器
asm运行时生成字节码为什么不用反射也能调用 getter/setter
util类型推断、反射、缓存等工具类型转换、类加载、泛型解析

3. 序列化流程:Java 对象如何变成 JSON 字符串

序列化入口通常是 JSON.toJSONString()。Fastjson 会创建输出缓冲区和序列化上下文,然后根据对象类型找到对应的 ObjectSerializer,把实际写入工作交给具体序列化器。

Person person = new Person();
person.setName("Alice");
person.setAge(18);

String json = JSON.toJSONString(person);

核心调用链可以简化成这样:

sequenceDiagram
    participant App as 业务代码
    participant JSON as JSON.toJSONString
    participant Writer as SerializeWriter
    participant Serializer as JSONSerializer
    participant Config as SerializeConfig
    participant ObjSer as ObjectSerializer
    participant FieldSer as FieldSerializer

    App->>JSON: toJSONString(object)
    JSON->>Writer: 创建输出缓冲区
    JSON->>Serializer: 创建 JSONSerializer
    Serializer->>Config: 根据 Class 查找 ObjectSerializer
    Config-->>Serializer: 返回具体序列化器
    Serializer->>ObjSer: write(serializer, object, ...)
    ObjSer->>FieldSer: 遍历字段并写入字段值
    FieldSer->>Writer: 写入字段名和值
    Writer-->>JSON: 输出 JSON 字符串
    JSON-->>App: 返回 String

3.1 入口初始化:配置、缓冲区、调度器

toJSONString() 的主干逻辑可以抽象成下面这样:

public static String toJSONString(Object object,
                                  SerializeConfig config,
                                  SerializeFilter[] filters,
                                  String dateFormat,
                                  int defaultFeatures,
                                  SerializerFeature... features) {
    SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);

    try {
        JSONSerializer serializer = new JSONSerializer(out, config);

        if (dateFormat != null) {
            serializer.setDateFormat(dateFormat);
        }

        if (filters != null) {
            for (SerializeFilter filter : filters) {
                serializer.addFilter(filter);
            }
        }

        serializer.write(object);
        return out.toString();
    } finally {
        out.close();
    }
}

这里有三个关键对象:

对象作用
SerializeConfig保存 Class -> ObjectSerializer 的缓存,避免每次都重新分析类型
SerializeWriter负责拼接 JSON 字符串,内部使用 char[] 缓冲
JSONSerializer序列化调度器,维护上下文、循环引用、过滤器和特性开关

3.2 JSONSerializer:按类型找到对应策略

JSONSerializer.write() 的核心逻辑很直接:空值直接写 null,非空对象按运行时类型查找序列化器。

public final void write(Object object) {
    if (object == null) {
        out.writeNull();
        return;
    }

    Class<?> clazz = object.getClass();
    ObjectSerializer writer = getObjectWriter(clazz);

    try {
        writer.write(this, object, null, null, 0);
    } catch (IOException e) {
        throw new JSONException(e.getMessage(), e);
    }
}

Fastjson 没有用一个大方法处理所有类型,而是把不同类型交给不同的序列化器。

Java 类型典型序列化器处理方式
StringStringCodec转义特殊字符后写入字符串
IntegerLong 等数字对应 Codec直接写数字文本
DateDateCodec按特性输出时间戳或格式化字符串
ListListSerializer遍历元素,递归序列化
MapMapSerializer遍历键值对
数组ArraySerializer按数组顺序写入
枚举EnumSerializer输出枚举名或 ordinal
JavaBeanJavaBeanSerializer 或 ASM 生成类遍历 getter/字段

SerializeConfig.getObjectWriter() 的查找顺序大致是:

flowchart TD
    A[根据 Class 查找序列化器] --> B{缓存命中?}
    B -->|是| Z[返回 ObjectSerializer]
    B -->|否| C[加载 SPI 扩展]
    C --> D[询问 Module 扩展]
    D --> E{是否内置类型?}
    E -->|Map/List/Date/Enum/数组等| F[使用内置序列化器]
    E -->|普通 JavaBean| G[创建 JavaBeanSerializer]
    G --> H{ASM 可用且类型适合?}
    H -->|是| I[生成 ASM 序列化器]
    H -->|否| J[使用反射序列化器]
    F --> K[写入缓存]
    I --> K
    J --> K
    K --> Z

这种设计属于策略模式:类型识别只负责选策略,真正写 JSON 的逻辑放在具体策略类里。


4. JavaBean 序列化:字段遍历、循环引用和输出缓冲

JavaBean 指符合 Java 常见约定的普通对象,一般有字段、getter、setter、默认构造器等。Fastjson 对 JavaBean 的处理最复杂,因为它要分析字段顺序、注解、过滤器、循环引用、运行时类型和格式化规则。

4.1 JavaBeanSerializer 的主流程

JavaBeanSerializer.write() 可以简化为几步:

protected void write(JSONSerializer serializer,
                     Object object,
                     Object fieldName,
                     Type fieldType,
                     int features,
                     boolean unwrapped) throws IOException {
    SerializeWriter out = serializer.out;

    if (object == null) {
        out.writeNull();
        return;
    }

    if (writeReference(serializer, object, features)) {
        return;
    }

    FieldSerializer[] fieldSerializers =
            out.sortField ? sortedGetters : getters;

    SerialContext parent = serializer.context;
    serializer.setContext(parent, object, fieldName, beanInfo.features, features);

    try {
        out.write('{');

        for (int i = 0; i < fieldSerializers.length; i++) {
            FieldSerializer fieldSerializer = fieldSerializers[i];

            Object value = fieldSerializer.getPropertyValue(object);
            Object processed = processValue(
                    serializer,
                    fieldSerializer.fieldContext,
                    object,
                    fieldSerializer.fieldInfo.name,
                    value,
                    features
            );

            fieldSerializer.writePrefix(serializer);
            fieldSerializer.writeValue(serializer, processed);
        }

        out.write('}');
    } finally {
        serializer.context = parent;
    }
}

这个流程里有三件事很重要:

  1. 先处理循环引用,避免对象图里出现环导致递归不结束。
  2. 按字段序列化器数组遍历属性,每个字段都有自己的 FieldSerializer
  3. 用上下文记录对象路径,后续生成 $ref 时需要知道当前对象在对象树中的位置。

4.2 循环引用:用 IdentityHashMap 记录对象身份

普通递归遍历遇到对象环会出问题。例如:

class Node {
    public String name;
    public Node next;
}

Node a = new Node();
Node b = new Node();
a.name = "a";
b.name = "b";
a.next = b;
b.next = a;

如果序列化时不做检测,流程会变成:

a -> b -> a -> b -> a -> ...

Fastjson 用 IdentityHashMap<Object, SerialContext> 记录已经处理过的对象。IdentityHashMap 比较的是对象身份,也就是 ==,不是 equals(),这更适合判断“是不是同一个对象实例”。

protected IdentityHashMap<Object, SerialContext> references;
protected SerialContext context;

public void setContext(SerialContext parent,
                       Object object,
                       Object fieldName,
                       int features,
                       int fieldFeatures) {
    if (out.disableCircularReferenceDetect) {
        return;
    }

    this.context = new SerialContext(parent, object, fieldName, features, fieldFeatures);

    if (references == null) {
        references = new IdentityHashMap<Object, SerialContext>();
    }

    references.put(object, context);
}

循环引用检测的逻辑可以概括为:

public boolean writeReference(JSONSerializer serializer, Object object, int fieldFeatures) {
    SerialContext context = serializer.context;

    int mask = SerializerFeature.DisableCircularReferenceDetect.mask;

    if (context == null || (context.features & mask) != 0 || (fieldFeatures & mask) != 0) {
        return false;
    }

    if (serializer.references != null && serializer.references.containsKey(object)) {
        serializer.writeReference(object);
        return true;
    }

    return false;
}

对象已经出现过时,Fastjson 不会重新展开它,而是写一个 $ref 引用。

{
  "name": "a",
  "next": {
    "name": "b",
    "next": {
      "$ref": ".."
    }
  }
}

SerializerFeature.DisableCircularReferenceDetect 可以关闭循环引用检测。没有环、对象图也不共享节点时,关闭它能省掉引用表维护成本;一旦对象图存在环,关闭后可能导致递归过深甚至 StackOverflowError

4.3 FieldSerializer:一个字段一个处理器

JavaBean 的每个属性都会对应一个 FieldSerializer。字段写入时并不只是 out.write(value),还要考虑运行时类型、格式化、注解和特性。

public void writeValue(JSONSerializer serializer, Object propertyValue) throws Exception {
    Class<?> runtimeFieldClass =
            propertyValue != null ? propertyValue.getClass() : fieldInfo.fieldClass;

    ObjectSerializer fieldSerializer =
            serializer.getObjectWriter(runtimeFieldClass);

    if (format != null && !(fieldSerializer instanceof DoubleSerializer)) {
        serializer.writeWithFormat(propertyValue, format);
        return;
    }

    fieldSerializer.write(
            serializer,
            propertyValue,
            fieldInfo.name,
            fieldInfo.fieldType,
            fieldFeatures
    );
}

这里用的是运行时类型,不是只看字段声明类型。例如字段声明为接口:

private Animal animal;

实际值可能是:

new Dog()

序列化时 Fastjson 会按 Dog.class 查找序列化器,而不是只按 Animal.class 处理。

4.4 SerializeWriter:减少字符串拼接开销

JSON 字符串构建是高频操作,如果每写一个字段就创建新字符串,内存分配会非常多。SerializeWriter 内部用 char[] 作为缓冲区,并通过 ThreadLocal 复用小缓冲。

private static final ThreadLocal<char[]> bufLocal = new ThreadLocal<char[]>();
private static final ThreadLocal<byte[]> bytesBufLocal = new ThreadLocal<byte[]>();
缓冲区用途典型大小回收策略
char[]拼接 JSON 字符串初始约 2048 字符不超过阈值时放回 ThreadLocal
byte[]UTF-8 编码转换初始约 8 KB不超过阈值时复用
大缓冲处理大 JSON按需扩容超过阈值不缓存,避免线程长期持有大数组

这种做法节省的是临时对象分配成本,尤其适合小对象、高频率序列化场景。


5. 反序列化流程:JSON 字符串如何变成 Java 对象

反序列化比序列化更复杂。序列化时对象结构已经存在,只要遍历并输出即可;反序列化需要从字符串里识别 token、匹配字段、推断类型、创建对象、处理引用、做安全检查,然后再赋值。

String text = "{\"name\":\"Alice\",\"age\":18}";
Person person = JSON.parseObject(text, Person.class);

核心调用链如下:

sequenceDiagram
    participant App as 业务代码
    participant JSON as JSON.parseObject
    participant Parser as DefaultJSONParser
    participant Lexer as JSONLexer
    participant Config as ParserConfig
    participant Deser as ObjectDeserializer
    participant BeanDeser as JavaBeanDeserializer
    participant FieldDeser as FieldDeserializer

    App->>JSON: parseObject(text, Person.class)
    JSON->>Parser: 创建 DefaultJSONParser
    Parser->>Lexer: 初始化词法分析器
    Parser->>Config: 根据 Type 查找 ObjectDeserializer
    Config-->>Parser: 返回反序列化器
    Parser->>Deser: deserialze(parser, type, fieldName)
    Deser->>Lexer: 扫描字段名和值
    Deser->>BeanDeser: 创建对象实例
    BeanDeser->>FieldDeser: 设置字段值
    Parser-->>JSON: 返回 Java 对象
    JSON-->>App: 返回 Person

5.1 parseObject:创建解析器并启动解析

JSON.parseObject() 的主干逻辑可以简化成:

public static <T> T parseObject(String text, Class<T> clazz, int features) {
    if (text == null) {
        return null;
    }

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

    try {
        T value = parser.parseObject(clazz);
        parser.handleResovleTask(value);
        return value;
    } finally {
        parser.close();
    }
}

几个关键点:

步骤作用
创建 DefaultJSONParser维护解析上下文、引用解析任务和词法分析器
使用 ParserConfig查找类型对应的 ObjectDeserializer
调用 parseObject(clazz)按目标类型解析 JSON
handleResovleTask处理 $ref 等延迟引用
close释放解析资源

5.2 ParserConfig:根据 Type 找反序列化器

反序列化器查找和序列化器查找类似,也会先查缓存。

public ObjectDeserializer getDeserializer(Type type) {
    ObjectDeserializer deserializer = deserializers.get(type);

    if (deserializer != null) {
        return deserializer;
    }

    if (type instanceof Class<?>) {
        return getDeserializer((Class<?>) type, type);
    }

    if (type instanceof ParameterizedType) {
        Type rawType = ((ParameterizedType) type).getRawType();

        if (rawType instanceof Class<?>) {
            return getDeserializer((Class<?>) rawType, type);
        }

        return getDeserializer(rawType);
    }

    return JavaObjectDeserializer.instance;
}

泛型反序列化时,不要只传 List.class,否则元素类型会丢失。更稳妥的写法是使用 TypeReference

List<Order> orders = JSON.parseObject(
        json,
        new TypeReference<List<Order>>() {}
);

6. JSONLexer:把字符串切成 token

反序列化的底层依赖词法分析器。JSONLexer 会扫描输入字符串,把 {}[]、字符串、数字、布尔值、null 等内容识别成 token,供上层解析器使用。

以整数解析为例,scanFieldInt() 做的事情可以拆成几个阶段:

flowchart TD
    A[当前位置尝试匹配字段名] --> B{字段名匹配?}
    B -->|否| C[标记 NOT_MATCH_NAME]
    B -->|是| D[读取字段值第一个字符]
    D --> E{是否负号?}
    E -->|是| F[记录 negative 并读取下一字符]
    E -->|否| G[进入数字解析]
    F --> G
    G --> H{是否 0-9?}
    H -->|否| I[标记 NOT_MATCH]
    H -->|是| J[十进制累加 value = value * 10 + digit]
    J --> K{下一个字符还是数字?}
    K -->|是| J
    K -->|否| L{遇到逗号或对象结束?}
    L -->|是| M[设置 token 和 matchStat]
    L -->|否| I
    M --> N[返回整数值]

简化后的伪代码如下:

public int scanFieldInt(char[] fieldName) {
    matchStat = UNKNOWN;

    if (!charArrayCompare(fieldName)) {
        matchStat = NOT_MATCH_NAME;
        return 0;
    }

    int offset = fieldName.length;
    char ch = charAt(bp + offset++);

    boolean negative = ch == '-';
    if (negative) {
        ch = charAt(bp + offset++);
    }

    int value = 0;

    if (ch < '0' || ch > '9') {
        matchStat = NOT_MATCH;
        return 0;
    }

    while (ch >= '0' && ch <= '9') {
        value = value * 10 + (ch - '0');
        ch = charAt(bp + offset++);
    }

    if (value < 0) {
        matchStat = NOT_MATCH;
        return 0;
    }

    if (ch == ',') {
        bp += offset;
        token = JSONToken.COMMA;
        matchStat = VALUE;
        return negative ? -value : value;
    }

    if (ch == '}') {
        // 根据后续字符设置 RBRACE、RBRACKET、EOF 等 token
        matchStat = END;
        return negative ? -value : value;
    }

    matchStat = NOT_MATCH;
    return 0;
}

这个方法有两个性能取向很明显:

  1. 字段名和字段值一起扫,命中时不用先切字符串再转换数字。
  2. 基础类型走专门路径,例如 intlongboolean 可以避免创建中间对象。

7. JavaBeanDeserializer:字段匹配、创建实例和赋值

普通对象反序列化主要由 JavaBeanDeserializer 完成。它需要在 JSON 字段和 Java 属性之间建立对应关系。

整体流程可以概括为:

flowchart TD
    A[进入 JavaBeanDeserializer] --> B[处理 null / JSONObject / 特殊 token]
    B --> C[获取 JSONLexer 和 ParseContext]
    C --> D[进入字段解析循环]
    D --> E{字段顺序是否命中预排序字段?}
    E -->|是| F[基础类型快速扫描]
    E -->|否| G[动态扫描字段名]
    F --> H{匹配成功?}
    H -->|是| I[保存或直接设置字段值]
    H -->|否| G
    G --> J{字段名是否为 $ref?}
    J -->|是| K[登记引用解析任务]
    J -->|否| L{字段名是否为 @type?}
    L -->|是| M[执行 AutoType 检查]
    L -->|否| N[查找 FieldDeserializer]
    M --> N
    N --> I
    I --> O{对象是否已创建?}
    O -->|否| P[调用构造器或工厂方法创建实例]
    O -->|是| Q[继续解析]
    P --> Q
    Q --> R{还有字段?}
    R -->|是| D
    R -->|否| S[通过 setter 或字段反射赋值]
    S --> T[返回对象]

7.1 字段匹配的快速路径

Fastjson 会把 JavaBean 字段提前排序并生成 FieldDeserializer[]。如果 JSON 字段顺序和 JavaBean 字段顺序接近,就可以按数组索引快速尝试匹配,减少哈希查找。

伪代码大致如下:

for (int fieldIndex = 0, notMatchCount = 0; ; fieldIndex++) {
    FieldDeserializer fieldDeserializer = null;
    FieldInfo fieldInfo = null;

    if (fieldIndex < sortedFieldDeserializers.length && notMatchCount < 16) {
        fieldDeserializer = sortedFieldDeserializers[fieldIndex];
        fieldInfo = fieldDeserializer.fieldInfo;
    }

    boolean matchField = false;
    Object fieldValue = null;

    if (fieldDeserializer != null) {
        char[] nameChars = fieldInfo.name_chars;
        Class<?> fieldClass = fieldInfo.fieldClass;

        if (fieldClass == int.class || fieldClass == Integer.class) {
            int intValue = lexer.scanFieldInt(nameChars);

            if (lexer.matchStat > 0) {
                fieldValue = intValue;
                matchField = true;
            } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {
                notMatchCount++;
                continue;
            }
        }

        // long、boolean、String 等类型也有类似快速路径
    }

    if (!matchField) {
        String key = lexer.scanSymbol(parser.symbolTable);
        // 动态字段名匹配、$ref、@type 都在这里处理
    }
}

notMatchCount < 16 是一个折中:字段顺序经常命中时走快速路径,连续多次不命中后就不要一直做无效尝试,改走动态字段名扫描。

7.2 对象实例化

字段解析过程中,Fastjson 需要创建目标对象。JavaBeanDeserializer.createInstance() 会根据类型情况选择不同方式。

类型情况创建方式
普通类有无参构造器通过反射调用默认构造器
存在工厂方法调用工厂方法
接口类型创建基于 JSONObject 的动态代理
非静态内部类需要依赖外部类实例
无可用构造器或工厂方法返回 null 或抛出异常

简化逻辑如下:

protected Object createInstance(DefaultJSONParser parser, Type type) {
    Class<?> clazz = (Class<?>) type;

    if (clazz.isInterface()) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        JSONObject backing = new JSONObject();

        return Proxy.newProxyInstance(
                loader,
                new Class<?>[] { clazz },
                backing
        );
    }

    if (beanInfo.defaultConstructor == null && beanInfo.factoryMethod == null) {
        return null;
    }

    try {
        Constructor<?> constructor = beanInfo.defaultConstructor;

        if (constructor.getParameterCount() == 0) {
            return constructor.newInstance();
        }

        ParseContext context = parser.getContext();

        if (context == null || context.object == null) {
            throw new JSONException("can't create non-static inner class instance.");
        }

        return constructor.newInstance(context.object);
    } catch (Exception e) {
        throw new JSONException("create instance error, class " + clazz.getName(), e);
    }
}

7.3 字段赋值:setter 优先,字段兜底

字段值解析出来后,会交给 FieldDeserializer.setValue() 写入对象。

public void setValue(Object object, Object value) {
    if (value == null && fieldInfo.fieldClass.isPrimitive()) {
        return;
    }

    if (fieldInfo.fieldClass == String.class
            && "trim".equals(fieldInfo.format)) {
        value = ((String) value).trim();
    }

    try {
        Method method = fieldInfo.method;

        if (method != null) {
            if (fieldInfo.getOnly) {
                Object current = method.invoke(object);

                if (current instanceof AtomicInteger) {
                    ((AtomicInteger) current).set(((AtomicInteger) value).get());
                } else if (current instanceof Map) {
                    ((Map) current).putAll((Map) value);
                } else if (current instanceof Collection) {
                    Collection collection = (Collection) current;
                    collection.clear();
                    collection.addAll((Collection) value);
                }
            } else {
                method.invoke(object, value);
            }
        } else if (fieldInfo.field != null) {
            fieldInfo.field.set(object, value);
        }
    } catch (Exception e) {
        throw new JSONException("set property error, " + fieldInfo.name, e);
    }
}

这里有几个细节:

  • 基础类型字段不能设置为 null,例如 int age 遇到 JSON 里的 null 会跳过。
  • format = "trim" 可以对字符串做裁剪。
  • 有 setter 时优先调用 setter。
  • 没有 setter 但字段可访问时,直接通过反射设置字段。
  • 只读集合、Map、AtomicInteger 等对象,会获取当前实例后修改内部内容。

8. ASM 加速:运行时生成专用序列化器

ASM 是一个 Java 字节码生成框架。Fastjson 1.x 会在运行时为部分 JavaBean 生成专用序列化器和反序列化器,把原本的反射调用变成直接方法调用。

可以把它理解成一条优化路径:

flowchart LR
    A[分析 JavaBean 字段和方法] --> B{是否适合 ASM?}
    B -->|否| C[使用反射序列化器 / 反序列化器]
    B -->|是| D[运行时生成字节码]
    D --> E[加载生成类]
    E --> F[直接调用 getter / setter / 字段访问]

反射路径类似这样:

Object value = getterMethod.invoke(object);
field.set(object, value);

ASM 生成的代码更接近手写代码:

String name = object.getName();
out.writeFieldValue(',', "name", name);

两者差异在于:

方式调用成本灵活性适合场景
反射每次调用有反射开销兼容性更好复杂类型、ASM 不适用场景
ASM 生成类接近直接方法调用生成条件受限制高频序列化/反序列化的普通 JavaBean

ASM 不是所有类型都能用。字段、访问级别、类加载器、泛型结构、特性开关等条件不满足时,Fastjson 会回退到反射实现。随着 JVM(Java Virtual Machine,Java 虚拟机)对反射性能不断优化,ASM 带来的优势会因场景而异,但在 Fastjson 1.x 的设计里,它仍然是高性能路径的重要组成部分。


9. AutoType:多态还原能力与安全风险

Java 里经常会把字段声明成接口、抽象类或父类:

interface Animal {
}

class Dog implements Animal {
    private String name;
    private double weight;

    public Dog() {
    }

    public Dog(String name, double weight) {
        this.name = name;
        this.weight = weight;
    }

    // getter / setter
}

class PetStore {
    private Animal animal;

    // getter / setter
}

如果只输出普通 JSON:

Animal dog = new Dog("dodi", 12);
PetStore store = new PetStore();
store.setAnimal(dog);

String json = JSON.toJSONString(store);
System.out.println(json);

结果大致是:

{
  "animal": {
    "name": "dodi",
    "weight": 12.0
  }
}

反序列化时,字段声明类型是 Animal,JSON 里又没有说明真实类型是 Dog,Fastjson 无法可靠地创建具体实现类。

AutoType 的作用就是把真实类型写进 JSON:

String json = JSON.toJSONString(
        store,
        SerializerFeature.WriteClassName
);

输出会包含类似 @type 的字段:

{
  "@type": "com.example.PetStore",
  "animal": {
    "@type": "com.example.Dog",
    "name": "dodi",
    "weight": 12.0
  }
}

反序列化时,Fastjson 看到 @type 后会尝试加载对应类,再创建实例并赋值。

ParserConfig.getGlobalInstance().addAccept("com.example.");

PetStore restored = JSON.parseObject(json, PetStore.class);
Dog parsedDog = (Dog) restored.getAnimal();

9.1 AutoType 的工作流程

flowchart TD
    A[JSON 中出现 @type] --> B[读取类型名]
    B --> C[ParserConfig.checkAutoType]
    C --> D{safeMode 是否开启?}
    D -->|是| E[拒绝动态类型]
    D -->|否| F[检查类型名长度和格式]
    F --> G[计算类型名 hash]
    G --> H{命中 deny 黑名单?}
    H -->|是| I[抛出异常]
    H -->|否| J{命中 accept 白名单或期望类型允许?}
    J -->|否| K[拒绝或返回空]
    J -->|是| L[加载 Class]
    L --> M[查找反序列化器]
    M --> N[创建实例并赋值]

checkAutoType() 会做多层检查,包括类型名长度、特殊前后缀、deny 列表、accept 列表和期望类型约束。Fastjson 为了减少字符串比较成本,会对类名计算 hash,再在排序后的 hash 数组里二分查找。

9.2 安全问题为什么集中在 AutoType

AutoType 的危险点在于:JSON 输入可以影响服务端加载哪个类,并触发这个类的 setter、构造逻辑或其他反序列化副作用

历史上,攻击者会构造带 @type 的 JSON,把类型指向某些危险类。例如部分 JNDI(Java Naming and Directory Interface,Java 命名和目录接口)相关类在 setter 被调用时可能访问外部 LDAP(Lightweight Directory Access Protocol,轻量目录访问协议)或 RMI(Remote Method Invocation,远程方法调用)服务,从而形成 RCE(Remote Code Execution,远程代码执行)风险。

风险链路可以抽象成:

flowchart LR
    A[攻击者提交 JSON] --> B[@type 指向危险类]
    B --> C[Fastjson 动态加载类]
    C --> D[反序列化调用 setter]
    D --> E[触发外部资源访问或危险逻辑]
    E --> F[RCE 或敏感操作]

Fastjson 1.x 早期主要依赖黑名单拦截危险类,但黑名单天然存在问题:只能拦住已知类,拦不住未知变体。后续版本加入了 safeMode,开启后会更严格地禁用 AutoType。

存量系统如果必须继续使用 Fastjson 1.2.83,安全建议非常明确:

ParserConfig.getGlobalInstance().setSafeMode(true);

同时避免这样做:

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

如果确实需要多态反序列化,优先考虑这些方式:

方案安全性说明
显式传入目标类JSON.parseObject(json, Dog.class),不依赖 @type
使用业务字段表达类型较高例如 "animalType": "dog",代码里手动分发
自定义反序列化器较高只允许业务明确支持的类型
addAccept("com.example.")只适合受控包名,仍需谨慎
全局开启 AutoType不适合处理不可信输入

10. 流式解析:大 JSON 文件不要一次性读进内存

普通 parseObject() 会把 JSON 结构完整解析出来。如果 JSON 是一个非常大的数组,例如几百万条订单,一次性解析成 List<Order> 很容易造成 OOM(Out Of Memory,内存溢出)。

Fastjson 1.x 提供了 JSONReader / JSONWriter,可以按流式方式逐条处理数据。流式解析一般采用 pull parsing,也就是业务代码主动“拉取”下一条对象。

try (JSONReader reader = new JSONReader(
        new InputStreamReader(
                new FileInputStream("huge-array.json"),
                StandardCharsets.UTF_8
        ))) {

    reader.startArray();

    while (reader.hasNext()) {
        Order order = reader.readObject(Order.class);

        processOrder(order);
        orderRepository.save(order);

        // 当前 order 处理完后即可等待 GC 回收
    }

    reader.endArray();
}

适合流式解析的 JSON 结构通常长这样:

[
  {"orderId": "1001", "amount": 99.5},
  {"orderId": "1002", "amount": 18.0},
  {"orderId": "1003", "amount": 42.8}
]

普通解析和流式解析的区别:

方式内存占用使用方式适合场景
parseObject / parseArray和 JSON 总数据量相关一次性得到完整对象小 JSON、接口请求体
JSONReader和单条对象大小相关一条一条读取大文件、批量导入、日志处理
JSONWriter不需要一次性构造完整字符串一条一条写出大结果集导出

流式解析的关键收益不是单条记录解析更快,而是不需要把全部数据同时放进内存


11. 常用特性和容易踩的坑

11.1 null 字段默认不输出

默认情况下,值为 null 的字段可能不会出现在 JSON 中。需要保留 null 字段时可以开启:

String json = JSON.toJSONString(
        user,
        SerializerFeature.WriteMapNullValue
);

常见 null 相关特性:

特性效果
WriteMapNullValue输出值为 null 的字段
WriteNullStringAsEmptyString 的 null 输出为 ""
WriteNullListAsEmptyList 的 null 输出为 []
WriteNullNumberAsZero数字 null 输出为 0
WriteNullBooleanAsFalse布尔 null 输出为 false

11.2 字段名和格式可以用注解控制

class User {
    @JSONField(name = "user_name")
    private String userName;

    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    private Date createdAt;

    @JSONField(serialize = false)
    private String password;

    // getter / setter
}
注解属性作用
name改 JSON 字段名
format格式化日期或字符串处理
serialize是否参与序列化
deserialize是否参与反序列化
ordinal控制字段输出顺序
serializeUsing指定自定义序列化器
deserializeUsing指定自定义反序列化器

11.3 接口字段和泛型字段要显式处理类型

接口字段:

class PetStore {
    private Animal animal;
}

泛型字段:

class Page<T> {
    private List<T> records;
}

这类结构在反序列化时都需要额外类型信息。更安全的方式不是依赖全局 AutoType,而是让业务代码明确告诉 Fastjson 目标类型:

List<Order> orders = JSON.parseObject(
        json,
        new TypeReference<List<Order>>() {}
);

或者用业务字段手动分发:

JSONObject obj = JSON.parseObject(json);
String type = obj.getString("animalType");

Animal animal;
if ("dog".equals(type)) {
    animal = obj.getObject("animal", Dog.class);
} else if ("cat".equals(type)) {
    animal = obj.getObject("animal", Cat.class);
} else {
    throw new IllegalArgumentException("unsupported animal type: " + type);
}

11.4 关闭循环引用检测要确认对象图没有环

String json = JSON.toJSONString(
        object,
        SerializerFeature.DisableCircularReferenceDetect
);

关闭后输出里不会出现 $ref,对接其他 JSON 库时更容易处理。但对象图中存在环时,递归序列化可能无法结束。

11.5 Fastjson 1.x 不适合继续作为新项目默认选择

Fastjson 1.x 的优势主要集中在小对象、高频 JSON 转换和存量生态兼容。新项目选型时可以按场景判断:

场景建议
存量系统已经大量使用 Fastjson 1.x升级到 1.2.83,并开启 safeMode,排查 AutoType 用法
新 Java 项目优先考虑 Fastjson 2.x 或 Jackson
需要多态反序列化避免全局 AutoType,使用白名单、自定义反序列化器或显式类型
处理外部不可信 JSON禁用 AutoType,不加载任意输入指定的类
超大 JSON 文件使用 JSONReader / JSONWriter 流式处理

12. 核心机制回顾

Fastjson 1.2.83 的主线可以压缩成两条链路。

序列化链路:

flowchart LR
    A[JSON.toJSONString] --> B[JSONSerializer]
    B --> C[SerializeConfig 查找 ObjectSerializer]
    C --> D[JavaBeanSerializer / 内置 Codec / ASM 生成类]
    D --> E[FieldSerializer 遍历字段]
    E --> F[SerializeWriter 写入 char 缓冲]
    F --> G[生成 JSON 字符串]

反序列化链路:

flowchart LR
    A[JSON.parseObject] --> B[DefaultJSONParser]
    B --> C[JSONLexer 扫描 token]
    B --> D[ParserConfig 查找 ObjectDeserializer]
    D --> E[JavaBeanDeserializer]
    E --> F[字段匹配与类型转换]
    F --> G[AutoType 检查]
    G --> H[创建对象实例]
    H --> I[setter / 字段反射赋值]
    I --> J[返回 Java 对象]

Fastjson 的速度来自几个具体设计:

  • 序列化器和反序列化器按类型缓存,减少重复分析。
  • 基础类型有专门 Codec,避免通用反射路径。
  • JSONLexer 直接扫描字符数组,尽量少创建中间字符串。
  • SerializeWriter 用可复用缓冲区拼接 JSON。
  • ASM 在适合的 JavaBean 上生成直接调用代码,减少反射成本。

它的风险也很集中:

  • AutoType 允许 JSON 影响类加载,处理不可信输入时必须严格关闭或限制。
  • 循环引用检测关闭后,需要保证对象图没有环。
  • 泛型、接口、抽象类字段需要明确类型策略。
  • Fastjson 1.x 已停止维护,新系统不应默认继续依赖它。

评论