1058 字
约 3 分钟
4
S06-B03 · 序列化:Java 原生 vs Jackson

适用读者:不知道什么时候用 Serializable、什么时候用 JSON 的人。
前置:S06-B02。
实测环境:Jackson 2.17.2 / JDK 17(2026-11-05)。
覆盖旧文:A-818(Java 序列化,74 点)、A-344(Jackson 自定义序列化,71 点)= 145 点。
覆盖扩展:E-S06-005(serialVersionUID)、E-S06-006(循环引用)。
上一篇:S06-B02 · Lombok:注解矩阵、原理与陷阱
下一篇:S06-B04 · JDK 自带工具类。


1. 一句话结论

Java 原生序列化(Serializable)用于"进程内/同构系统间";跨语言、跨版本、对外接口一律用 JSON(Jackson)。混用是事故来源。

维度 Java 原生 Jackson(JSON)
格式 二进制(私有) 文本(可读)
跨语言 ❌ ✅
版本兼容 靠 serialVersionUID + 手工维护 靠字段可选/默认值
安全 高危(反序列化漏洞) 需同样防(多态类型)
体积 较小 较大

2. serialVersionUID 到底管什么(E-S06-005)

事实 说明
不写会自动生成(按类结构算哈希) 类一改,UID 就变 → 反序列化失败
写死一个值 类结构小改仍能反序列化(但可能字段丢失)
版本不匹配 InvalidClassException

判据:"这个类会不会被序列化后长期存放/跨版本传输?" 会 → 显式声明 serialVersionUID。


3. 实测:Jackson 的序列化与反序列化

ObjectMapper m = new ObjectMapper();
String json = m.writeValueAsString(new Demo("张三", 30));
Demo d2 = m.readValue(json, Demo.class);

实测输出:

3. Jackson 序列化: {"name":"张三","age":30}
4. 反序列化: Demo(name=张三, age=30)

注意:反序列化需要无参构造(或用 @JsonCreator)。这就是 B02 里"@Data 没有无参构造"的坑在同一处爆炸。


4. 循环引用:为什么直接炸(E-S06-006)

class A { B b = new B(); String name = "a"; }
class B { A a; String name = "b"; }
a.b.a = a;   // 互相引用
m.writeValueAsString(a);

实测输出:

循环引用序列化异常: JsonMappingException
原因 说明
JSON 是树结构 环无法表示
序列化会无限递归 Jackson 检测到就抛异常(而不是栈溢出)

三种解法:

解法 写法 适用
忽略字段 @JsonIgnore 断掉环
只序列化 ID @JsonIdentityInfo 需要引用关系
DTO 转换 序列化时用扁平 DTO 推荐(避免把实体泄露给外部)

判据:"对外接口不该直接返回实体"——实体有双向关联、有懒加载、有内部字段。


5. 自定义序列化(A-344)

需求 做法
日期格式 @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
脱敏 自定义 JsonSerializer(手机号中间四位打码)
枚举输出中文 @JsonValue 或自定义
忽略 null @JsonInclude(NON_NULL)
public class PhoneSerializer extends JsonSerializer<String> {
    public void serialize(String v, JsonGenerator g, SerializerProvider p) throws IOException {
        g.writeString(v.substring(0,3) + "****" + v.substring(7));
    }
}

6. 五个误区

误区 真相
"Serializable 是标配" 只有需要时才加;加了就要管 UID
"不写 UID 更省事" 类一改就反序列化失败
"JSON 序列化不会出错" 循环引用/懒加载/多态都会炸
"实体可以直接当接口返回值" 泄露内部结构、触发懒加载
"反序列化只是解析数据" 不可信输入的反序列化可能 RCE(J02)

7. 自检

  1. Java 原生与 JSON 的四个关键差异?
  2. 不写 serialVersionUID 会发生什么?
  3. 循环引用为什么必然失败?
  4. 三种解法各自适用什么场景?
  5. 为什么"实体不该直接作为接口返回值"?

8. 下一篇

S06-B04 · JDK 自带工具类:Arrays/Collections/Objects/Scanner

序列化讲完,下一篇回到"少写代码":JDK 已经提供的工具方法,以及它们各自的边界。


本篇属于系列《S06 · Java 工程化与组件》。旧文来源:A-818/A-344;扩展点 E-S06-005/006。Jackson 2.17.2 输出为实测。

S06-B03 · 序列化:Java 原生 vs Jackson
https://www.clxhxhhr.top/posts/5257/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。