管理序列化Java对象的多个版本

管理序列化Java对象的多个版本,java,serialization,Java,Serialization,假设我有一个程序,出于某种原因需要处理序列化对象的旧版本 反序列化时,可能会遇到其中一个版本 class Pet { private static final long serialVersionUID = 1L; int paws; } class Pet { private static final long serialVersionUID = 2L; long paws; // handle marsian centipedes boolean

假设我有一个程序,出于某种原因需要处理序列化对象的旧版本

反序列化时,可能会遇到其中一个版本

class Pet {
    private static final long serialVersionUID = 1L;
    int paws;
}

class Pet {
    private static final long serialVersionUID = 2L;
    long paws; // handle marsian centipedes
    boolean sharpTeeth;
}
让我们假设(逻辑上)可以使用一些巧妙的策略来设置不存在的字段等,将旧对象转换为新对象,但是:

我如何安排我的源代码?在编写转换器时,我可能需要在同一个源代码树中使用这两个版本,但在eclipse中如何处理呢

我应该在一个类加载器中进行反序列化,如果失败,请尝试使用另一个使用旧版本的类加载器(等等),或者有更好的方法吗


最好的策略是什么

您不必维护类的多个版本。最新版本就足够了。请参见专门的“重构序列化类”链接。

不幸的是,不允许更改字段类型。支持两个(十个,一百个?)不同的版本太难了。因此,您可以使用
readObject(ObjectInputStream in)
方法。并设置一个固定的
serialVersionUID
。如果您最初没有设置它,请使用IDE或JDK
serialver
获取它,这样看起来您只有一个版本的类

如果要更改字段的类型,请同时更改其名称。例如
paws
pawscont
。如果字段中存在类型不匹配,反序列化机制甚至无法访问
readObject(..)
方法

对于上述示例,可行的解决方案是:

class Pet implements Serializable {
    private static final long serialVersionUID = 1L;
    long pawsCount; // handle marsian centipedes
    boolean sharpTeeth;

    private void readObject(java.io.ObjectInputStream in)
        throws IOException, ClassNotFoundException {

        in.defaultReadObject();
        GetField fields = in.readFields();
        int paws = fields.get("paws", 0); // the 0 is a default value 
        this.pawsCount = paws;
    }
}
稍后添加的字段将设置为其默认值

顺便说一句,它可能更容易使用(如果对您的项目来说还不算太晚的话)

我应该一次反序列化吗 类装入器,如果失败,请尝试 使用另一个使用 旧版本(等等),或者 有更好的方法吗

最好的策略是什么

序列化确实不应该用于长期存储

这里最好的策略是使用数据库:将对象存储在
Pets
表中,然后当您更改表中的字段时,所有旧数据也会更新,每个对象都具有相同且最新的模式

这确实是为长期存储维护数据的最佳方法,而且更新旧对象以填充空字段非常容易

让我们假设(逻辑上)可以使用一些巧妙的策略来设置不存在的字段等,将旧对象转换为新对象。。。我如何安排我的源代码

我认为有两种处理方法。首先,除非希望抛出
InvalidClassException
,否则永远不要更改
serialVersionUID
。第二条规则是不更改字段的类型,而只添加或删除序列化自动处理的字段。例如,如果序列化文件的类版本具有
布尔锐齿
但该类没有该字段,则在反序列化期间将忽略该字段。如果反序列化的类有
sharptooth
字段,但文件没有,那么在本例中,它将被初始化为默认值
false

这对于希望同时处理向前和向后兼容性的分布式系统尤其重要。您不希望升级应用程序a的某个版本并破坏依赖于a的另一个应用程序B。通过不更改
serialVersionUID
而只是添加或删除字段,您就可以做到这一点。实体的更高版本需要支持新字段中没有值的旧版本,但旧实体不会介意新字段是否可用。这也意味着您不应该更改字段的比例

序列化非常智能,但它不处理字段的类型更改。你不应该只是把爪子从
int
改成
long
。相反,我建议添加一个
long pawsLong
或类似的代码,并编写代码来处理
int paws
long pawsLong
有值的可能性

public long getPaws() {
    if (pawsLong > 0) {
        return pawsLong;
    } else {
        // paws used to be an integer
        return paws;
    }
}
您还可以编写自己的
readObject
方法,在反序列化时进行转换:

private void readObject(java.io.ObjectInputStream in) {
    super.readObject(in);
    // paws used to be an integer
    if (pawsLong == 0 && paws != 0) {
        pawsLong = paws;
    }
}
如果这对您不起作用,那么定制序列化是一种方法。但是,您必须从头开始执行此操作,并使用内部版本id定义自定义
readObject(…)
writeObject(…)
方法。例如:

// never change this
private static final long serialVersionUID = 3375159358757648792L;
// only goes up
private static final int INTERNAL_VERSION_ID = 2;
...
// NOTE: in version #1, this was an int
private long paws;

private void readObject(java.io.ObjectInputStream in) {
    int version = in.readInt();
    switch (version) {
        case 1 :
            paws = in.readInt();
            ...
        case 2 :
            paws = in.readLong();
            ...

private void writeObject(java.io.ObjectOutputStream out) {
    out.writeInt(INTERNAL_VERSION_ID);
    out.writeLong(paws);
    ...
但是这种方法不能帮助您实现向前兼容性。版本1读取器无法理解版本2序列化输入

我应该在一个类加载器中进行反序列化,如果失败,请尝试使用另一个使用旧版本的类加载器(等等),或者有更好的方法吗


我不会建议任何这些方法。听起来很难维持。

回答得不错,尽管这不是我想要的。如果我问你在哪里回答这个问题,我想我会使用Hibernate:-)我认为DB对多个部署没有任何帮助。你必须维护ORM映射和一系列数据库更新脚本等等。回答得好!现在还不算晚,因为这不是一个项目。。。实际上,我正在考虑实现和使用纯对象数据库时的一些问题,…:)@wlfbck为什么它不再工作?我已经检查了文档,似乎没有任何内容发生更改/被弃用。@BenediktM。删除了我的评论,我错误地查看了带有ObjectInput in的readExternal,它没有readFields()。如果我们要序列化/反序列化为JSON,是否无法避免更改数据类型的问题?在JSON中,不同的类型并不明显。例如Integer和BigInteg