> 技术文档 > 在 Java 世界里让对象“旅行”:序列化与反序列化

在 Java 世界里让对象“旅行”:序列化与反序列化


        Java 生态里关于 JSON 的序列化与反序列化(以下简称“序列化”)是一个久经考验的话题,却常因框架繁多、配置琐碎而让初学者望而却步。本文将围绕一段极简的 JsonUtils 工具类展开,以 FastJSON 与 Jackson 两大主流实现为例,从原理到实践、从特性到隐患,做一次系统梳理。文章力求以学术写作之严谨,帮助读者在 3000 字左右完成一次由点及面的进阶。


目录

一、为什么需要“工具类”而非直接调用框架 API

示例代码段

二、FastJSON 实现细节与行为解读

2.1 序列化:统一日期格式与循环引用控制

2.2 反序列化:TypeReference 的价值

2.3 异常策略:IllegalArgumentException 而非底层异常

三、Jackson 实现细节与行为解读

3.1 ObjectMapper 的线程安全

3.2 空 Bean 与日期格式

3.3 异常处理:IOException 的简化

四、横向对比:FastJSON vs Jackson

五、从工具类到项目落地:一个完整的演进故事

5.1 迁移步骤

5.2 兼容性陷阱

六、再谈防御式编程:边界条件的“三重门”

七、小结与展望


一、为什么需要“工具类”而非直接调用框架 API

        无论 FastJSON 还是 Jackson,其 API 都足够简洁:JSON.toJSONString(obj)objectMapper.writeValueAsString(obj) 即可完成序列化。然而生产环境中,我们往往需要在“一致性”“防御式编程”“可追踪”“可扩展”四个维度做额外约束。

  1. 一致性:日期格式、空值策略、循环引用检测等行为必须全局统一。
  2. 防御式编程:对 null、空串、非法 JSON 的入参给出明确兜底。
  3. 可追踪:异常信息须携带上下文(对象类型、原始 JSON 片段)。
  4. 可扩展:未来切换实现(如从 FastJSON 迁移到 Jackson)时业务代码零改动。

        因此,一个 JsonUtils 的存在绝非“重复造轮子”,而是对底层实现做“策略封装”。下文的两段代码正是这一思路的极简落地。

示例代码段

//FastJSONpublic final class JsonUtils { private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class); // ========== 构造器 ========== private JsonUtils() {} // ========== 序列化 ========== public static String toJson(Object obj) { if (obj == null) { return \"null\"; } try { return JSON.toJSONString(obj,  SerializerFeature.DisableCircularReferenceDetect,  SerializerFeature.WriteDateUseDateFormat); // 统一日期格式 } catch (Exception e) { logger.error(\"Serialize object to JSON failed. Object={}\", obj, e); throw new IllegalArgumentException(\"JSON serialize error\", e); } } // ========== 反序列化(单个对象) ========== public static  T fromJson(String json, Class clazz) { if (json == null || json.isEmpty()) { return null; } try { return JSON.parseObject(json, clazz); } catch (Exception e) { logger.error(\"Deserialize JSON to {} failed. JSON={}\", clazz.getSimpleName(), json, e); throw new IllegalArgumentException(\"JSON deserialize error\", e); } } // ========== 反序列化(复杂泛型,如 List) ========== public static  T fromJson(String json, TypeReference typeRef) { if (json == null || json.isEmpty()) { return null; } try { return JSON.parseObject(json, typeRef); } catch (Exception e) { logger.error(\"Deserialize JSON to {} failed. JSON={}\", typeRef.getType(), json, e); throw new IllegalArgumentException(\"JSON deserialize error\", e); } }}
//Jacksonpublic final class JsonUtils { private static final ObjectMapper MAPPER = new ObjectMapper() .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) .setDateFormat(new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\")); private JsonUtils() {} public static String toJson(Object obj) { if (obj == null) return \"null\"; try { return MAPPER.writeValueAsString(obj); } catch (JsonProcessingException e) { throw new IllegalArgumentException(\"Serialize error\", e); } } public static  T fromJson(String json, Class clazz) { if (json == null || json.isEmpty()) return null; try { return MAPPER.readValue(json, clazz); } catch (IOException e) { throw new IllegalArgumentException(\"Deserialize error\", e); } }}

二、FastJSON 实现细节与行为解读

        FastJSON 由阿里巴巴开源,以“快”著称,实现上大量依赖 ASM 动态字节码生成,将反射开销降至极低。在给出的 FastJSON 版 JsonUtils 中,三条语句几乎涵盖日常 90% 的场景。

2.1 序列化:统一日期格式与循环引用控制
return JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteDateUseDateFormat);
  • DisableCircularReferenceDetect 关闭循环引用检测。FastJSON 默认会为循环引用生成 $ref,这在 RESTful 返回中常因前端无法解析而踩坑。关闭后,若实际出现循环引用将直接抛 JSONException,用“快速失败”换取“数据干净”。

  • WriteDateUseDateFormat 强制使用全局日期格式(yyyy-MM-dd HH:mm:ss)。FastJSON 内部维护一个 DateFormat 线程局部变量,因此该配置对性能几乎无损耗。

2.2 反序列化:TypeReference 的价值
public static  T fromJson(String json, TypeReference typeRef)

        Java 类型擦除导致 List 在运行时只剩 List。FastJSON 的 TypeReference 借助匿名内部类保存泛型签名,绕过擦除,反序列化时即可还原完整类型。这一点在 Jackson 中对应 TypeReference 同名类,设计思路如出一辙。

2.3 异常策略:IllegalArgumentException 而非底层异常

        FastJSON 抛出的 JSONException 继承自 RuntimeException,工具类将其包装为 IllegalArgumentException,语义上更接近“参数非法”。这一转换使得调用方无需显式捕获受检异常,同时保持日志链路完整。


三、Jackson 实现细节与行为解读

        Jackson 是 Spring 生态的默认 JSON 方案,模块丰富、扩展点繁多。在 JsonUtils 的 Jackson 实现中,配置集中在静态 ObjectMapper 的初始化块。

3.1 ObjectMapper 的线程安全

        官方文档明确指出:ObjectMapper 在配置完成后是线程安全的。因此工具类将其声明为 static final,避免重复创建带来的元数据开销(SerializerProviderDeserializerCache 等)。但需注意,若在运行时调用 setXxx 方法修改配置,则线程安全假设将被打破。

3.2 空 Bean 与日期格式
MAPPER.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) .setDateFormat(new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\"));
  • FAIL_ON_EMPTY_BEANS 默认开启,当对象无任何可序列化属性时抛异常。关闭后,此类对象会被序列化为 {},避免 DTO 在演进过程中因新增字段全部 @JsonIgnore 而意外崩溃。

  • SimpleDateFormat 非线程安全,但 ObjectMapper 会将其包裹成线程局部变量,因此配置一次即可。

3.3 异常处理:IOException 的简化

        Jackson 的 writeValueAsString 声明抛出 JsonProcessingException(继承 IOException)。工具类同样将其转换为 IllegalArgumentException,与 FastJSON 保持行为统一,降低上层心智负担。


四、横向对比:FastJSON vs Jackson

维度 FastJSON Jackson 性能 高(ASM 生成字节码) 中高(3.x 版本已大幅优化) 默认日期格式 时间戳 时间戳 循环引用处理 默认使用 $ref 默认抛出 JsonMappingException 泛型反序列化 TypeReference TypeReference(同名类) 安全配置(autoType) 曾出现 RCE 漏洞,需开启 safemode 默认白名单机制,漏洞面更小 社区活跃度 国内高,国际一般 国际主流,Spring 默认 扩展性 支持 SerializeFilter 等扩展 模块机制丰富(Joda、Kotlin 等)

注:性能差异在大多数业务场景下可忽略,应优先考虑可维护性与安全。


五、从工具类到项目落地:一个完整的演进故事

        假设某电商系统早期采用 FastJSON,后因安全审计要求全面迁移至 Jackson。若直接使用框架 API,则改动面巨大;而借助 JsonUtils,仅需替换实现即可。

5.1 迁移步骤
  1. 保留原有 JsonUtils 类签名,内部实现替换为 Jackson。
  2. 通过全局搜索验证无直接调用 JSON.parseXxx 的代码。
  3. 运行单元测试,重点观察日期格式、Long 型精度、BigDecimal 精度是否变化。
  4. 灰度发布,通过日志比对线上 JSON 输出差异。
5.2 兼容性陷阱
  • 浮点精度:FastJSON 默认关闭 WriteNullNumberAsZero,Jackson 需手动配置 SerializationFeature.WRITE_NULL_NUMBERS_AS_ZERO

  • Long 精度:前端 JavaScript 最大安全整数为 2^53-1,后端 Long 超过此范围需序列化为字符串。FastJSON 可配置 BrowserCompatible,Jackson 需自定义 ToStringSerializer


六、再谈防御式编程:边界条件的“三重门”

工具类虽小,却肩负第一道防线。以下三点常被忽视:

  1. null 与空串:FastJSON 允许 JSON.parseObject(\"\", clazz) 返回 null,而 Jackson 会抛异常。工具类统一返回 null,避免调用方差异。

  2. 异常日志:必须记录原始 JSON 片段,但需脱敏(如手机号、身份证)。可引入 SPI 机制,让业务模块提供 SensitiveDataFilter

  3. 线程局部泄漏:若使用 ThreadLocal 缓存 SimpleDateFormat,务必在 Tomcat 热部署时调用 remove,防止类加载器泄露。


七、小结与展望

        序列化是“数据在 JVM 与网络之间最后一公里”的工程。FastJSON 与 Jackson 各有千秋,工具类则是屏蔽差异、沉淀团队规范的最佳载体。未来随着 Java 21 的 Vector API、Project Valhalla 的 value objects 落地,序列化的底层实现或将迎来新一轮变革。但万变不离其宗:统一配置、防御式编程、可观测三板斧,仍将长期适用。


        希望这篇 3000 字左右的梳理,能为你下一次技术选型或代码审查,提供一把“小而锋利”的瑞士军刀。