Java序列化的这三个坑千万要小心

本文通过一个2016年的故障复盘,深入探讨Java序列化中的常见问题,包括类实现接口未指定`serialVersionUID`、父类指定而子类未指定以及枚举类型在序列化中的风险。这些问题可能导致RPC接口成功率骤降,理解并避免这些坑对于Java开发者至关重要。
摘要由CSDN通过智能技术生成

前几天看到一个2016年挺有趣的一个故障复盘,有一哥们给底层的HSF服务返回值加了一个字段,秉承着“加字段一定是安全的”这种惯性思维就直接上线了,上线后发现这个接口成功率直接跌0,下游的服务抛出类似下面这个异常堆栈

java.io.InvalidClassException:com.taobao.query.TestSerializable;
	local class incompatible: stream classdesc serialVersionUID = -7165097063094245447,local class    serialVersionUID = 6678378625230229450

看到这个堆栈可能有老司机已经反应过来了,下面我们就看下这种异常到底是如何发生的

Java序列化与反序列化

  • 序列化:将对象写入到IO流中
  • 反序列化:从IO流中恢复对象

序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。

要想有序列化的能力,得实现Serializable接口,就像下面的这个例子一样:

public class SerializableTest implements Serializable {
   
    private static final long serialVersionUID = -3751255153289772365L;
}

这里面一个关键的点是serialVersionUID,JVM会在运行时判断类的serialVersionUID来验证版本一致性,如果传来的字节流中的serialVersionUID与本地相应类的serialVersionUID相同则认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。

在上面的例子中,我们通过IDEA的插件已经自动为SerializableTest生成了一个serialVersionUID,如果我们不指定serialVersionUID,编译器在编译的时候也会根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段 。

Dubbo与序列化

/dev-guide/images/dubbo-extension.jpg

图片来源:https://dubbo.apache.org/zh/docs/v2.7/dev/design/

从Dubbo的调用链可以发现是有一个序列化节点的,其支持的序列化协议一共有四种:

  1. dubbo序列化:阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它
  2. hessian2序列化:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubbo RPC默认启用的序列化方式
  3. json序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实现都不是特别成熟,而且json这种文本序列化性能一般不如上面两种二进制序列化。
  4. java序列化:主要是采用JDK自带的Java序列化实现,性能很不理想。

从那个帖子看当时HSF服务提供集群设置的序列化方式是java序列化,而不是像现在一样默认hessian2,如果在RPC中使用了Java序列化,那下面的这三个坑一定注意不要踩

类实现了Serializable接口,但是却没有指定serialVersionUID

我们之前在文中提过,如果实现了Serializable的类没有指定serialVersionUID,编译器编译的时候会根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,这就决定了这个类在序列化上一定不是向前兼容的,前文中的那个故障就是踩了这个坑。我们在本地模拟一下这个case:

假如我们先有Student这样的一个类

public class Student implements Serializable {
   

    private static int startId = 1000;

    private int id;

    public Student() {
   
        id = startId ++;
    }
}

我们将其序列化到磁盘:

private static void serialize() {
   
    try {
   

        Student student = new Student();
        FileOutputStream fileOut =
                new FileOutputStream("/tmp/student.ser");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        out.writeObject(student);
        out.close(
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值