Oops! 更改field的数据类型,影响到rabbitmq消费了...(有关于Java序列化)

news/2025/2/22 21:40:54

程序中有如下entity类-LevyPaymentFlow

java">@Data
@TableName(value = "levy_payment_flow", autoResultMap = true)
public class LevyPaymentFlow implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private String flowNo;

    /**
     * 状态:初始、付款中、付款成功、付款失败
     * @see c.e.c.enums.LevyPaymentFlowStatusEnum
     */
    private String status;

    ...
}

为了让代码更易读、易维护,我将其status的类型从String改成对应的枚举 LevyPaymentFlowStatusEnum ,相关的业务代码跟着一并更改。

以我的经验,这个小重构万无一失。

结果上线过程中翻车,现实给我上了一课。

什么问题呢? rabbitmq消费受到影响了。

业务程序中rabbitmq生产消息,将 LevyPaymentFlow 对象直接写入到消息队列中。
上线后的一刻,rabbitmq消费者监听并消费的时候出现异常:ClassCastException: cannot assign instance of java.lang.String to field c.e.c.levypay.entity.LevyPaymentFlow.status of type c.e.c.enums.LevyPaymentFlowStatusEnum in instance of c.e.c.levypay.entity.LevyPaymentFlow(不能将String串赋值给LevyPaymentFlow对象的LevyPaymentFlowStatusEnum类型的status,或者说,不能将String串分配到LevyPaymentFlowStatusEnum类型的LevyPaymentFlow#status上)

这是 java反序列化失败导致的异常。

Java反序列化对象时,当类结构发生变更时,会报上面的 ClassCastException。我们常用的fastjson则可以兼容这个结构的变更,可以兼容本案中的将String串赋值给枚举field的场景。

java序列化我本地测试来复现一下这个bug">关于Java序列化,我本地测试来复现一下这个bug

第一步,执行下面test代码,将 LevyPaymentFlow 对象序列化为字节流并保存到文件中。

java">FileOutputStream byteArrayOutputStream=new FileOutputStream("testJavaSerialize");
	ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
	LevyPaymentFlow entity = new LevyPaymentFlow();
	entity.setStatus(LevyPaymentFlowStatusEnum.PAY_SUCCESS.getCode());
	objectOutputStream.writeObject(entity);

第二步,变更 LevyPaymentFlow#status 的类型,改为 LevyPaymentFlowStatusEnum。
第三步,下面代码从上面创建的文件中读取字节流并将其反序列化为 LevyPaymentFlow 对象。执行程序抛出 ClassCastException。

java">ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("testJavaSerialize"));
	LevyPaymentFlow entity2= (LevyPaymentFlow)objectInputStream.readObject();

异常堆栈:

java">Exception in thread "main" java.lang.ClassCastException: cannot assign instance of java.lang.String to field c.e.c.levypay.entity.LevyPaymentFlow.status of type c.e.c.enums.LevyPaymentFlowStatusEnum in instance of c.e.c.levypay.entity.LevyPaymentFlow
	at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2301)
	at java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.java:1431)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2437)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2355)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2213)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1669)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
	at TestMain.main(TestMain.java:82)

java序列化补充两点">关于Java序列化。补充两点,

一)要使用Java序列化,对象类必须实现java.io.Serializable 接口。否则,上面第一步的序列化代码会报错

java">Exception in thread "main" java.io.NotSerializableException: c.e.c.levypay.entity.LevyPaymentFlow
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at TestMain.main(TestMain.java:75)

二)关于序列化机制中的 serialVersionUID

serialVersionUID 是一个 private static final long 类型的常量,存在于实现了 java.io.Serializable 接口的类中。它作为类的一个版本标识,用于在序列化和反序列化过程中验证序列化对象和类的版本一致性。如果不手动指定 serialVersionUID,JVM 在序列化时会根据类的结构信息自动计算并生成一个 serialVersionUID 值。

如果未指定serialVersionUID,那么,当类的结构发生变更,反序列化时会因serialVersionUID值不同而抛出 InvalidClassException

java">Exception in thread "main" java.io.InvalidClassException: c.e.c.levypay.entity.LevyPaymentFlow; local class incompatible: stream classdesc serialVersionUID = -7534543191229157756, local class serialVersionUID = -9139150564353461074
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2005)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1852)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2186)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1669)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
	at TestMain.main(TestMain.java:80)

http://www.niftyadmin.cn/n/5862785.html

相关文章

【git】合并多个提交记录

1.说明 有时候因为某些小修改会导致代码多了很多不必要的提交,如果希望合并这些commit记录,我们可以使用rebase或者reset命令完成合并 2.rebase 这种方式是通过变基操作完成的,它适用于已经push到远程的情况,当我们变基好了之后…

RocketMq\Kafka如何保障消息不丢失?

程序那点事 保证RocketMq和Kafka消息不丢失需考虑Producer发送、Broker存储、Consumer消费。需配置同步发送/刷盘、重试机制、幂等性生产,手动提交偏移量等策略。摘要由作者通过智能技术生成 RocketMq架构图 RocketMq消息不丢失 要想保证消息不丢失,需…

《重构-》

一、代码坏的味道 神秘命名 ​​​​​代码应该直观明了。要深思熟虑如何给函数、模块、变量和类命名,使它们能清晰地表明 自己的功能和用法。 重复代码 一旦有重复代码存在,阅读这些重复的代码时你就必须加倍仔细,留意其间细微的差异。如果…

git使用-克隆远程项目、分支管理

文章目录 克隆远程项目到本地1. 远程找到需要克隆的项目,复制ssh地址2. idea开启git版本控制(如果已经开了,忽略此步骤)3. clone远端项目4. 克隆完成 分支管理1. 新建分支2. 切换分支3. 合并分支4. 储存变化 克隆远程项目到本地 …

软件架构设计:软件工程

一、软件工程概述 软件工程的定义 软件工程是应用系统化、规范化、可量化的方法开发、运行和维护软件。 软件工程的目标 提高软件质量、降低开发成本、缩短开发周期。 软件生命周期 瀑布模型:需求分析→设计→编码→测试→维护。迭代模型:分阶段迭代开…

【Leetcode 每日一题】2209. 用地毯覆盖后的最少白色砖块

问题背景 给你一个下标从 0 0 0 开始的 二进制 字符串 f l o o r floor floor,它表示地板上砖块的颜色。 f l o o r [ i ] floor[i] floor[i] 为 ‘0’ 表示地板上第 i i i 块砖块的颜色是 黑色 。 f l o o r [ i ] floor[i] floor[i] 为’1’ 表示地板上第 i …

【够用就好005】-在VSCode中管理ECS服务器的实操步骤

前景提要:接触过云服务器,当前有一个可以使用的ecs服务器。 关于如何搭建配置云服务不在今天分享主题内。 亲测有效!!! 通过 VSCode 直接配置服务器步骤 一.先安装ssh插件 CTRL shift x 插件界面输入ssh安装remot…

Golang连接使用SqlCipher

一、准备环境 需要下载MinGW、msys2、OpenSSL,并且注意都需要64位 已经整理成环境软件包,只需要下载,并配置环境变量 链接: https://pan.baidu.com/s/1NxF8aWqx7s97ntACOk77Ug 提取码: yhrv 二、代码 package mainimport ("database/s…