如何在Java中将类重构为接口时支持向后兼容的序列化?

时间:2012-08-15 16:41:48

标签: java serialization backwards-compatibility

不幸的是,问题标题的情况在过去已经发生了几年。

我有一个Id接口,扩展了Serializable并包含名称和ID号的getter。有一个匹配的IdImpl类来实现接口。但是,在过去的某个时刻,Id 类。还有一个可序列化的容器对象,其成员字段类型为Id。这些容器对象已被序列化到数据库多年,因此容器对象的版本包含两种类型的Id。在尝试反序列化旧对象时,我们得到InvalidClassException。如何反序列化包含旧Id具体类实例的旧容器对象?

完全披露:多年来对Id界面进行了其他一些更改,但我认为它们看起来像compatible changes(向IdImpl添加了一个字段并且是一个获取者到Id获取“String idType”; generified Comparable)。其中一个变化是否也会导致问题?

这些类看起来像这样:

// current Id interface
public interface Id extends Serializable, Comparable<Id> {
    String getName();
    int getIdNumber();
}

// current Id implementation
public class IdImpl implements Id {
    private static final long serialVersionUID = 10329865109284L;
    private String name;
    private int idNumber;
    IdImpl(String name, int idNumber) { this.name = name; this.idNumber = idNumber; }
    @Override public String getName() { return name; }
    @Override public int getIdNumber() { return idNumber; }
    @Override public int compareTo(Id id) { /* some code here */ }
}

// the container object
public ContainerForm implements Serializable {
    private static final long serialVersionUID = -3294779665912049275L;
    private String someField;
    private Id user;
    private String someOtherField;
    // getters and setters
}

// this is what the _old_ Id concrete class looked like
// (from source control history; not in current project)
public class Id implements Serializable, Comparable {
    // never had a serialVersionUID
    private String name;
    private int idNumber;
    Id(String name, int idNumber) { this.name = name; this.idNumber = idNumber; }
    public String getName() { return name; }
    public int getIdNumber() { return idNumber; }
    public int compareTo(Object id) { /* some code here */ }
}

尝试反序列化容器对象时得到的异常是:

java.io.InvalidClassException: mypkg.people.Id; local class incompatible: stream classdesc serialVersionUID = -6494896316839337071, local class serialVersionUID = -869017349143998644
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1947)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1871)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
    at mypkg.forms.FormFactory.getForm(FormFactory.java:3115)
    ... 34 more

1 个答案:

答案 0 :(得分:6)

作为一般规则,使用serialVersionUID并将类转换为接口是一个非常困难的过渡支持。

在此特定情况下,我相信您可以通过以下方式支持旧的Id实施:

  • 创建一个类(称为OldId),该类具有旧的serialVersionUID和Id的旧实现(它应该实现Id)。
  • 创建ObjectInputStream的自定义子类并覆盖readClassDescriptor()方法。调用父readClassDescriptor和
    • 如果返回的ObjectStreamClass具有Id类名和旧的serialVersionUID,则返回新OldId类的ObjectStreamClass
    • 否则返回给定描述符