
线上事故回顾
不久前,在为一个简单功能上线前进行代码审查时,出于记录日志的目的,我临时添加了一行简单的日志代码。本以为这行无关紧要的日志不会引发任何问题,没想到上线后系统立刻触发了一系列警报。我迅速执行了回滚操作,定位并删除了那行日志代码后,才得以重新成功上线。
情景还原
问题的根源在于一个简单的 CountryDTO 对象。
public class CountryDTO {
private String country;
public void setCountry(String country) {
this.country = country;
}
public String getCountry() {
return this.country;
}
public Boolean isChinaName() {
return this.country.equals("中国");
}
}
定义测试类 FastJonTest 并执行序列化。
public class FastJonTest {
@Test
public void testSerialize() {
CountryDTO countryDTO = new CountryDTO();
String str = JSON.toJSONString(countryDTO);
System.out.println(str);
}
}
运行时报空指针错误:

通过报错信息可以清晰看出,在序列化过程中执行了 isChinaName() 方法,而此时 this.country 成员变量为空。这引出了两个关键问题:
- 为什么序列化会执行
isChinaName() 方法?
- 序列化过程究竟会执行哪些方法?
源码分析
通过调试观察调用链的堆栈信息,我们可以深入其内部机制。


调用链中的 ASMSerializer_1_CountryDTO.write 表明,FastJson 使用 ASM 技术动态生成了一个类 ASMSerializer_1_CountryDTO。ASM 技术的一个典型应用场景就是通过动态生成类来替代 Java 反射调用,从而避免重复执行时的反射开销。
JavaBeanSerializer 序列化原理
如下图所示,序列化的核心逻辑在 JavaBeanSerializer 类的 write() 方法中。

JavaBeanSerializer 主要通过 getObjectWriter() 方法获取。通过跟踪 getObjectWriter() 的执行过程,可以定位到关键方法 com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer,并进一步找到 com.alibaba.fastjson.util.TypeUtils#computeGetters。
public static List<FieldInfo> computeGetters(Class<?> clazz,
// ...
PropertyNamingStrategy propertyNamingStrategy
) {
// 省略部分代码....
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// 省略部分代码...
if (method.getReturnType().equals(Void.TYPE)) {
continue;
}
if (method.getParameterTypes().length != 0) {
continue;
}
// 省略部分代码...
JSONField annotation = TypeUtils.getAnnotation(method, JSONField.class);
// 省略部分代码...
if (annotation != null) {
if (!annotation.serialize()) {
continue;
}
if (annotation.name().length() != 0) {
// 省略部分代码...
}
}
if (methodName.startsWith("get")) {
// 省略部分代码...
}
if (methodName.startsWith("is")) {
// 省略部分代码...
}
}
}
从代码逻辑可以看出,FastJson 在决定是否序列化一个方法时,主要考察以下几种情况:
- 是否存在
@JSONField(serialize = false, name = "xxx") 注解。
- 方法名是否以
get 开头。
- 方法名是否以
is 开头。
序列化流程图
下图清晰地展示了 FastJson 序列化过程中,筛选 Java Bean 方法的完整流程:

示例代码分析
为了验证上述规则,我们使用一个更复杂的 CountryDTO 进行测试。
/**
* case1: @JSONField(serialize = false)
* case2: getXxx()返回值为void
* case3: isXxx()返回值不等于布尔类型
* case4: @JSONType(ignores = "xxx")
*/
@JSONType(ignores = "otherName")
public class CountryDTO {
private String country;
public void setCountry(String country) {
this.country = country;
}
public String getCountry() {
return this.country;
}
public static void queryCountryList() {
System.out.println("queryCountryList()执行!!");
}
public Boolean isChinaName() {
System.out.println("isChinaName()执行!!");
return true;
}
public String getEnglishName() {
System.out.println("getEnglishName()执行!!");
return "lucy";
}
public String getOtherName() {
System.out.println("getOtherName()执行!!");
return "lucy";
}
/**
* case1: @JSONField(serialize = false)
*/
@JSONField(serialize = false)
public String getEnglishName2() {
System.out.println("getEnglishName2()执行!!");
return "lucy";
}
/**
* case2: getXxx()返回值为void
*/
public void getEnglishName3() {
System.out.println("getEnglishName3()执行!!");
}
/**
* case3: isXxx()返回值不等于布尔类型
*/
public String isChinaName2() {
System.out.println("isChinaName2()执行!!");
return "isChinaName2";
}
}
运行序列化测试,控制台输出与最终生成的 JSON 如下:
isChinaName()执行!!
getEnglishName()执行!!
{"chinaName":true,"englishName":"lucy"}
结果分析:
isChinaName() 和 getEnglishName() 被调用并序列化。
queryCountryList():static 方法,不符合条件。
getOtherName():被 @JSONType(ignores = "otherName") 显式忽略。
getEnglishName2():被 @JSONField(serialize = false) 显式忽略。
getEnglishName3():返回 void,不符合条件。
isChinaName2():返回类型非布尔,不符合条件。
代码规范建议
从以上分析可见,FastJson 的序列化规则涉及多种判断条件:返回值类型、参数数量、@JSONType 和 @JSONField 注解等。在团队开发中,如果存在多种实现或规避方式,很容易因成员间知识掌握程度的差异而引入潜在问题。因此,确立一种清晰、一致的推荐方案至关重要。
这里强烈推荐使用 @JSONField(serialize = false) 来显式地标注不参与序列化的方法。采用此方案后的代码如下所示,哪些方法会被排除在序列化之外,一目了然,极大地增强了代码的可读性和可维护性。
public class CountryDTO {
private String country;
public void setCountry(String country) {
this.country = country;
}
public String getCountry() {
return this.country;
}
@JSONField(serialize = false)
public static void queryCountryList() {
System.out.println("queryCountryList()执行!!");
}
public Boolean isChinaName() {
System.out.println("isChinaName()执行!!");
return true;
}
public String getEnglishName() {
System.out.println("getEnglishName()执行!!");
return "lucy";
}
@JSONField(serialize = false)
public String getOtherName() {
System.out.println("getOtherName()执行!!");
return "lucy";
}
@JSONField(serialize = false)
public String getEnglishName2() {
System.out.println("getEnglishName2()执行!!");
return "lucy";
}
@JSONField(serialize = false)
public void getEnglishName3() {
System.out.println("getEnglishName3()执行!!");
}
@JSONField(serialize = false)
public String isChinaName2() {
System.out.println("isChinaName2()执行!!");
return "isChinaName2";
}
}
总结
回顾整个处理流程:发现问题 -> 原理分析 -> 解决问题 -> 总结规范,这是一个非常经典的技术问题处理与学习路径。
- 从业务视角:核心在于解决问题 -> 评估并选择最优解决方案 -> 思考如何将优秀实践推广到更多系统。
- 从技术视角:通过解决一个具体问题,可以深入理解一整条技术线(如序列化)的工作原理,这是掌握Java和SpringBoot等后端技术底层逻辑的有效方式。