java序列化

问题

最近在dubbo接口扩展上遇到了问题。dubbo的参数及返回对象,肯定是要可序列化的,即实现Serializable接口。需求是需要在接口参数中,加入一个字段,但是担心对原来的consumer产生影响,因此对java序列化进行了一下梳理测试。

顺便说下关于dubbo接口扩展碰到的这个问题,有几点收获:

  • 接口的传参,尽量用对象代替多个简单类型的参数,后者不便于加参数
  • 返回数据,同样尽量用对象代替简单类型
  • 更好的参数或返回数据扩展方案,应该是采用继承原有参数或返回类型的方式

序列化

java序列化,就是将java对象序列化为字节流,可以进行传递或者保存,在使用方对结果进行反序列化,从而获取到原来对象的属性值。

在需要将内存中对象保存到文件,或者直接传输对象时,会用到序列化。dubbo就是在provider和consumer之间传递对象数据。

类定义改变

回到最初的问题,其根本是java类定义在发送方发生改变后,接收方能否正确反序列化数据。

单元测试代码如下:

    class Person implements Serializable {

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    class NewPerson implements Serializable {

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

    }

    class ChildPerson extends Person {

        private int age;

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

    @Test
    public void testSerialize() throws IOException, ClassNotFoundException {
        Person parent = new Person();
        parent.setName("Parent");

        String parentSerFile = "parent.ser";
        String childSerFile = "child.ser";

        // 序列化parent到parentSerFile
        serialize(parent, parentSerFile);

        Person person = unSerialize(parentSerFile);
        System.out.println(person.getName()); // OK

//        NewPerson newPerson = unSerialize(parentSerFile); // java.lang.ClassCastException
//        System.out.println(newPerson.getName());

        // 序列化child到childSerFile
        ChildPerson child = new ChildPerson();
        child.setName("Child");
        child.setAge(10);
        serialize(child, childSerFile);

        person = unSerialize(childSerFile);
        System.out.println(person.getName()); // OK
        System.out.println(((ChildPerson)person).getAge());

    }

    /**
     * 序列化对象,保存到文件
     */
    private void serialize(Object obj, String fileName) throws IOException {
        FileOutputStream fs = new FileOutputStream(fileName);
        ObjectOutputStream os = new ObjectOutputStream(fs);
        os.writeObject(obj);
        os.close();
    }

    /**
     * 反序列化对象
     */
    private <T> T unSerialize(String fileName) throws IOException, ClassNotFoundException {
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(fileName));
        T obj = (T) inputStream.readObject();
        inputStream.close();
        return obj;
    }

类路径及类名必须一致

发送方类com.test1.Class1,接收方是com.test1.Class2或者com.test2.Class1,都不能正确反序列化,报java.lang.ClassCastException异常,如上述代码中注掉的newPerson部分。

类定义发生改变

若类定义发生改变,即发送方和接收方,虽然类路径和类名一致,但是发送方和接收方的类版本等不一致,此时亦会报错。

如上述代码,在序列化生成parent.ser文件后,将Person类添加字段sex,如下:

    class Person implements Serializable {

        private String name;
        private String sex;

        ...

然后进行反序列化,会抛出异常java.io.InvalidClassException。提示:local class incompatible: stream classdesc serialVersionUID = 4485681234731380735, local class serialVersionUID = 5015652288950510004

这就是serialVersionUID的作用了。在Person的类定义中,加入以下代码就OK了:

private static final long serialVersionUID = 4485681234731380735L;

因此,在定义可序列化对象的时候,强烈建议显式定义serialVersionUID,防止类由于版本等的问题,无法匹配从而无法反序列化问题。

在Eclipse中,是会自动提示生成随机serialVersionUID的,在Intellij idea中同样可以,需要开启:File | Settings | Editor | Inspections 中 Java | Serialization issues | Serializable class without ‘serialVersionUID’ , 开启后,用alt+enter就可以显式生成serialVersionUID了。

更好的扩展方式

更好的扩展方式,应该是定义可序列化对象类的子类,将子类对象序列化后,仍然可以反序列化为原父类对象,从而对原来的序列化数据接收方无影响。

如上述测试代码中的将ChildPerson类的对象序列化后的文件,反序列化为Person类对象。

在一个已成熟稳定的系统中,扩展的时候,应尽量采用这种方式;但由于我们的系统刚刚搭建完成,因此我直接显式声明了serialVersionUID

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值