java序列化

来源:互联网 发布:Java捕获控制台 编辑:程序博客网 时间:2024/06/05 00:38

问题

最近在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

0 0