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 数据结构 | JSON、JSONObject、JSONArray |
| 配置管理层 | 缓存类型到序列化器/反序列化器的映射,管理特性开关 | SerializeConfig、ParserConfig |
| 序列化引擎 | 把 Java 对象写成 JSON 字符串 | JSONSerializer、ObjectSerializer、SerializeWriter |
| 反序列化引擎 | 把 JSON 字符串解析成 Java 对象 | DefaultJSONParser、ObjectDeserializer、JSONLexer |
| 安全防护层 | 限制动态类型加载,降低反序列化攻击风险 | checkAutoType、safeMode |
| 扩展层 | 支持自定义字段名、格式、序列化器和反序列化器 | @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 容器 | toJSONString、parseObject 怎么进入内部流程 |
serializer | Java 对象转 JSON | 字段为什么没输出、null 怎么处理、循环引用怎么写 |
parser | JSON 转 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 类型 | 典型序列化器 | 处理方式 |
|---|---|---|
String | StringCodec | 转义特殊字符后写入字符串 |
Integer、Long 等数字 | 对应 Codec | 直接写数字文本 |
Date | DateCodec | 按特性输出时间戳或格式化字符串 |
List | ListSerializer | 遍历元素,递归序列化 |
Map | MapSerializer | 遍历键值对 |
| 数组 | ArraySerializer | 按数组顺序写入 |
| 枚举 | EnumSerializer | 输出枚举名或 ordinal |
| JavaBean | JavaBeanSerializer 或 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;
}
}
这个流程里有三件事很重要:
- 先处理循环引用,避免对象图里出现环导致递归不结束。
- 按字段序列化器数组遍历属性,每个字段都有自己的
FieldSerializer。 - 用上下文记录对象路径,后续生成
$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;
}
这个方法有两个性能取向很明显:
- 字段名和字段值一起扫,命中时不用先切字符串再转换数字。
- 基础类型走专门路径,例如
int、long、boolean可以避免创建中间对象。
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 的字段 |
WriteNullStringAsEmpty | String 的 null 输出为 "" |
WriteNullListAsEmpty | List 的 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 已停止维护,新系统不应默认继续依赖它。