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

打印 上一主题 下一主题

主题 993|帖子 993|积分 2979

程序中有如下entity类-LevyPaymentFlow
  1. @Data
  2. @TableName(value = "levy_payment_flow", autoResultMap = true)
  3. public class LevyPaymentFlow implements Serializable {
  4.     private static final long serialVersionUID = 1L;
  5.     @TableId(type = IdType.AUTO)
  6.     private String flowNo;
  7.     /**
  8.      * 状态:初始、付款中、付款成功、付款失败
  9.      * @see c.e.c.enums.LevyPaymentFlowStatusEnum
  10.      */
  11.     private String status;
  12.     ...
  13. }
复制代码
为了让代码更易读、易维护,我将其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

第一步,执行下面test代码,将 LevyPaymentFlow 对象序列化为字节流并保存到文件中。
  1.         FileOutputStream byteArrayOutputStream=new FileOutputStream("testJavaSerialize");
  2.         ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
  3.         LevyPaymentFlow entity = new LevyPaymentFlow();
  4.         entity.setStatus(LevyPaymentFlowStatusEnum.PAY_SUCCESS.getCode());
  5.         objectOutputStream.writeObject(entity);
复制代码
第二步,变更 LevyPaymentFlow#status 的类型,改为 LevyPaymentFlowStatusEnum。
第三步,下面代码从上面创建的文件中读取字节流并将其反序列化为 LevyPaymentFlow 对象。执行程序抛出 ClassCastException。
  1.         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("testJavaSerialize"));
  2.         LevyPaymentFlow entity2= (LevyPaymentFlow)objectInputStream.readObject();
复制代码
异常堆栈:
  1. 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
  2.         at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2301)
  3.         at java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.java:1431)
  4.         at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2437)
  5.         at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2355)
  6.         at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2213)
  7.         at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1669)
  8.         at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
  9.         at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
  10.         at TestMain.main(TestMain.java:82)
复制代码
关于Java序列化。补充两点,

一)要使用Java序列化,对象类必须实现java.io.Serializable 接口。否则,上面第一步的序列化代码会报错
  1. Exception in thread "main" java.io.NotSerializableException: c.e.c.levypay.entity.LevyPaymentFlow
  2.         at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
  3.         at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
  4.         at TestMain.main(TestMain.java:75)
复制代码
二)关于序列化机制中的 serialVersionUID
serialVersionUID 是一个 private static final long 类型的常量,存在于实现了 java.io.Serializable 接口的类中。它作为类的一个版本标识,用于在序列化和反序列化过程中验证序列化对象和类的版本一致性。如果不手动指定 serialVersionUID,JVM 在序列化时会根据类的结构信息自动计算并天生一个 serialVersionUID 值。
如果未指定serialVersionUID,那么,当类的结构发生变更,反序列化时会因serialVersionUID值不同而抛出 InvalidClassException
  1. Exception in thread "main" java.io.InvalidClassException: c.e.c.levypay.entity.LevyPaymentFlow; local class incompatible: stream classdesc serialVersionUID = -7534543191229157756, local class serialVersionUID = -9139150564353461074
  2.         at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
  3.         at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2005)
  4.         at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1852)
  5.         at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2186)
  6.         at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1669)
  7.         at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
  8.         at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
  9.         at TestMain.main(TestMain.java:80)
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天空闲话

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表