找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

975

积分

0

好友

139

主题
发表于 4 天前 | 查看: 14| 回复: 0

图片

线上事故回顾

不久前,在为一个简单功能上线前进行代码审查时,出于记录日志的目的,我临时添加了一行简单的日志代码。本以为这行无关紧要的日志不会引发任何问题,没想到上线后系统立刻触发了一系列警报。我迅速执行了回滚操作,定位并删除了那行日志代码后,才得以重新成功上线。

情景还原

问题的根源在于一个简单的 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 成员变量为空。这引出了两个关键问题:

  1. 为什么序列化会执行 isChinaName() 方法?
  2. 序列化过程究竟会执行哪些方法?

源码分析

通过调试观察调用链的堆栈信息,我们可以深入其内部机制。

图片
img

调用链中的 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 在决定是否序列化一个方法时,主要考察以下几种情况:

  1. 是否存在 @JSONField(serialize = false, name = "xxx") 注解。
  2. 方法名是否以 get 开头。
  3. 方法名是否以 is 开头。

序列化流程图

下图清晰地展示了 FastJson 序列化过程中,筛选 Java Bean 方法的完整流程:

序列化.png

示例代码分析

为了验证上述规则,我们使用一个更复杂的 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";
    }
}

总结

回顾整个处理流程:发现问题 -> 原理分析 -> 解决问题 -> 总结规范,这是一个非常经典的技术问题处理与学习路径。

  • 从业务视角:核心在于解决问题 -> 评估并选择最优解决方案 -> 思考如何将优秀实践推广到更多系统。
  • 从技术视角:通过解决一个具体问题,可以深入理解一整条技术线(如序列化)的工作原理,这是掌握JavaSpringBoot等后端技术底层逻辑的有效方式。



上一篇:QuickRecorder:不到5MB的macOS开源录屏工具,替代付费方案
下一篇:SQL窗口函数实战:LAG与LEAD函数在时间序列数据分析中的应用
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 22:11 , Processed in 0.191434 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表