【问题标题】:Java de-serialization backward compatibilityJava反序列化向后兼容
【发布时间】:2015-12-21 16:29:32
【问题描述】:

如何反序列化序列化后修改的类?

更具体地说,我知道当一个类在其初始版本中具有serialVersionUID 时可以做到这一点。没有serialVersionUID的课程有什么办法吗?

我有一个对象

package com.test.serialize;

import java.io.Serializable;

public class MyObject implements Serializable{

    String name;

    public String getName() {
        return name;
    }

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

}

我像这样序列化类

package com.test.serialize;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeTest {

    public static void main(String[] args) {

        try {
            MyObject myObject = new MyObject();
            myObject.setName("Ajit");

            ObjectOutputStream objectOStr = null;
            ByteArrayOutputStream byteOStr = null;
            try {
                byteOStr = new ByteArrayOutputStream();
                objectOStr = new ObjectOutputStream(byteOStr);
                objectOStr.writeObject(myObject);

            } catch (IOException e) {
                System.out.println(e);
            } finally {
                try {
                    if (objectOStr != null)
                        objectOStr.close();
                } catch (IOException e2) {
                }
            }
            FileOutputStream fo = new FileOutputStream(new File("serialize"));
            fo.write(byteOStr.toByteArray());
            fo.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

像这样反序列化

package com.test.serialize;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.RandomAccessFile;

public class DeserializeTest {

    public static void main(String[] args) {

        try {

    //          File f = new File("serialize");
    //          FileInputStream fs = new FileInputStream(f);
            RandomAccessFile raF = new RandomAccessFile("serialize", "r");
            byte[] b = new byte[(int)raF.length()];
            raF.read(b);

            ObjectInputStream oIstream = null;
            ByteArrayInputStream bIstream = null;

            bIstream = new ByteArrayInputStream(b);
            oIstream = new ObjectInputStream(bIstream);
            Object finalResult = oIstream.readObject();
            System.out.println(finalResult.toString());
        } catch (IOException | ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

一段时间后,我添加了

@Override
public String toString() {
    return "MyObject [name=" + name + ", names=" + names + "]";
}

MyObject。添加之后,我得到了像

这样的异常
java.io.InvalidClassException: com.test.serialize.MyObject; local class in 
compatible: stream classdesc serialVersionUID = 5512234731442983181, local class
serialVersionUID = -6186454222601982895
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at com.test.serialize.DeserializeTest.main(DeserializeTest.java:25)     

请帮帮我。

【问题讨论】:

  • 您应该将新版本的serialVerionUID 设置为旧版本的readObject/writeObject 方法。
  • 如果您启用所有编译器警告并注意它们,编译器会告诉您这将会发生,因为您没有提供 serialVersionUID 字段。

标签: java serialization deserialization


【解决方案1】:

感谢@Gábor Bakos。

这可以通过为旧类创建 serialVersionUID(签名应与序列化期间的签名相同)并在当前类中添加该 serialVersionUID 来解决。

serialver -classpath /***PATH***/bin com.test.serialize.MyObject

返回

com.test.serialize.MyObject:    static final long serialVersionUID = 5512234731442983181L;

之后我将它添加到我的 MyObject 中,如下所示

package com.test.serialize;

import java.io.Serializable;

public class MyObject implements Serializable{

    /**
     * Added serial version Id of old class, created before adding new fields
     */
    private static final long serialVersionUID = 5512234731442983181L;


public MyObject() {
    System.out.println("Constructor");
}

String name;


public String getName() {
    return name;
}

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


String names ="Altered after change!";

public String getNames() {
    return names;
}

public void setNames(String names) {
    System.out.println("Setting names");
    this.names = names;
}

@Override
public String toString() {
    return "MyObject [name=" + name + ", names=" + names + "]";
}

}

效果很好。

更多信息请参考:serialver

【讨论】:

    【解决方案2】:

    第一个建议:使用序列化,因为一切都差不多完成了。

    第二个建议:使用 serialVersionUID 并使用一个版本来修复它:这里是为了警告您并防止不同序列化版本之间的混淆。

    所以:如果您更改字段或字段的含义,请更改 serialVersionUID。

    那么你有向后兼容性问题。

    请参阅此处了解许多想法:Managing several versions of serialized Java objects

    恕我直言:

    • 无论您采用何种解决方案,请记住您的程序将管理带有部分数据的对象:然后您必须管理所有带有或不带有数据的案例。

    • 如果您不经常更改您的版本:使用几个不同的类。也许是子类或接口的实现:然后您可以获得程序,并管理对象的多个版本:MyClass_V1、MyClass_V2 等。当您反序列化时,您可以测试/重试并获得好的对象。之后,您可能需要在类之间转换数据

    • 如果更改版本,通过添加新字段(不更改旧字段),会更容易一些(子类,转换直接到父类)

    • 或者您可以考虑使用 XML 结构来序列化和反序列化:您可以具有向后和向前兼容性,因为它是可扩展的:字段存在或为空。您必须自己管理映射或使用一些库。

    希望对你有帮助!

    【讨论】:

    • 随着每个新的班级版本更改 serialVersionUID 是一个显然从未真正做过的人提出的都市神话。如果您真的这样做了,您将立即拥有一个无法运行的系统。这个想法根本不需要改变它,并管理对类的更改,以使它们保持在对象序列化规范的版本控制章节中指定的范围内。
    • javadoc 也是某种都市神话:“一个serialVersionUID,在反序列化期间用于验证序列化对象的发送方和接收方是否已为该对象加载了与序列化兼容的类。”然后兼容=您可以保留版本号。不兼容=改变它。有了它,版本管理就可以如愿以偿。其他神奇的棘手的自我管理当然很有趣,但我不明白重新发明这个轮子的好处,也不确定它的连续性......
    【解决方案3】:

    我会记住以下几点,

    1. 每个Serializable 类都包含一个serialVersionUID(无论您是否明确指定了它都没有关系)。
    2. 有兼容的更改,也有不兼容的更改
      例如添加新字段是兼容更改,删除字段不是兼容更改。添加/删除/编辑方法通常是兼容的更改,但在您的情况下肯定不是这样(添加toString() 方法后serialVersionUID 发生了更改)

    3.在修改类之前,您可以使用serialver实用程序找到旧类的serialVersionUID并在新类中使用它

    别以为还有什么魔术:)

    【讨论】:

    • 删除字段不会阻止类被反序列化。该对象被正确读入,但存在于流中但不在类中的任何字段基本上都被丢弃。类中存在但流中不存在的任何字段都设置为默认值 (null/0)。
    猜你喜欢
    • 2011-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-08
    • 2012-04-17
    • 1970-01-01
    • 1970-01-01
    • 2018-03-01
    相关资源
    最近更新 更多