Java 反序列化向后兼容性


如何

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

更具体地说,我知道当一个类在其初始版本中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)     

请帮我解决这个问题。

感谢@Gábor巴科斯。

这可以通过为旧类创建 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 + "]";
}
}

它工作正常。

更多信息参考: 串行器

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

第二个建议:使用serialVersionUID并保持一个版本:它在这里警告你并防止不同序列化版本之间的混淆。

所以:如果你改变字段或字段的含义,改变serialVersionUID。

然后你就有了向后兼容性问题。

有关许多想法,请参阅此内容:管理序列化 Java 对象的多个版本

恕我直言:

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

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

  • 如果您更改版本,通过添加新字段(不更改旧字段(,则更容易一些(子类,转换直接发送给父级(

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

希望对您有所帮助!

我会记得以下几点,

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

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

不要以为还有其他魔术:)