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. 自检
- Java 原生与 JSON 的四个关键差异?
- 不写
serialVersionUID会发生什么? - 循环引用为什么必然失败?
- 三种解法各自适用什么场景?
- 为什么"实体不该直接作为接口返回值"?
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/ 评论
0 条
还没有评论,先写一条吧。