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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3678136/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 02:58:33  来源:igfitidea点击:

Managing several versions of serialized Java objects

javaserialization

提问by KarlP

Lets say that I have a program that for some reason need to handle old versions of serialized objects.

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

Eg: when deserializing, one of these versions may be encountered.

例如:反序列化时,可能会遇到这些版本之一。

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;
}

Lets assume that it's (logically) possible to convert an old object to a new object using some clever strategy to set nonexistant fields etc etc, but:

让我们假设(逻辑上)可以使用一些巧妙的策略来设置不存在的字段等,将旧对象转换为新对象,但是:

How do I arrange my source code? I would probably need both versions in the same source tree when writing a converter, but how do I handle that in , say, eclipse.

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

Should I do deserialization in one class loader, and if that fails try using another class loader that uses an older version (and so on), or are there better ways?

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

What's the best strategy?

最好的策略是什么?

采纳答案by Gray

Lets assume that it's (logically) possible to convert an old object to a new object using some clever strategy to set nonexistant fields etc etc... How do I arrange my source code?

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

I see two ways of handling this. First off, you should never change the serialVersionUIDunless you want InvalidClassExceptionto be thrown. The second rule is to notchange the typesof fields but to only add or remove fields which serialization handles automagically. For example, if a serialized file has the version of the class which has boolean sharpTeeth;but the class doesn't have that field then it will be ignored during deserialization. If the deserialized class has the sharpTeethfield but the file doesn't then it will get initialized to its default value, falsein this case.

我看到了两种处理方法。首先,serialVersionUID除非你想InvalidClassException被抛出,否则你永远不应该改变。第二条规则是不要更改字段的类型,而只能添加或删除序列化自动处理的字段。例如,如果序列化文件具有类的版本,boolean sharpTeeth;但该类没有该字段,那么在反序列化期间它将被忽略。如果反序列化的类具有该sharpTeeth字段但文件没有,则false在这种情况下,它将被初始化为其默认值。

This is especially important with distributed systems where you want to try to handle both forwards and backwards compatibility. You don't want to upgrade a version of application A and break another application B which depends on A. By not changing the serialVersionUIDbut just adding or removing fields you can do that. Later versions of your entity need to support older versions without values in newer fields but older entities won't mind if new fields are available. This also means that you shouldn't change a field's scale as well.

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

Serialization is pretty smart but it does not handle type changes to fields. You shouldn't just change pawsfrom an intto a long. Instead, I'd recommend adding a long pawsLongor some such and writing your code to handle the possibility of there being int pawsor long pawsLonghaving a value.

序列化非常聪明,但它不处理字段的类型更改。您不应该只是paws从 an更改int为 a long。相反,我建议添加一个long pawsLong或一些这样的并编写您的代码来处理存在int pawslong pawsLong具有值的可能性。

public long getPaws() {
    if (pawsLong > 0) {
        return pawsLong;
    } else {
        // paws used to be an integer
        return paws;
    }
}

You could also write your own readObjectmethod to do the conversion at de-serialization time:

您还可以编写自己的readObject方法在反序列化时进行转换:

private void readObject(java.io.ObjectInputStream in) {
    super.readObject(in);
    // paws used to be an integer
    if (pawsLong == 0 && paws != 0) {
        pawsLong = paws;
    }
}

If this doesn't work for you then custom serialization is the way to go. You have to start from the beginning doing this however and define custom readObject(...)and writeObject(...)methods with an internal version id. Something like:

如果这对您不起作用,那么自定义序列化是可行的方法。但是,您必须从头开始执行此操作,readObject(...)writeObject(...)使用内部版本 ID定义自定义和方法。就像是:

// 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);
    ...

But this method does not help you with forwards compatibility. A version 1 reader won't understand version 2 serialization input.

但是这种方法不能帮助您实现向前兼容性。版本 1 的读者不会理解版本 2 的序列化输入。

Should I do deserialization in one class loader, and if that fails try using another class loader that uses an older version (and so on), or are there better ways?

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

I would not suggest any of these methods. Sounds very difficult to maintain.

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

回答by Bozho

Unfortunately, changing field types is not allowed. Supporting two (ten, hundred?) different versions would be too much of an effort. So you can utilize the readObject(ObjectInputStream in)method. And set a fixed serialVersionUID. If you haven't set it initially, use your IDE or the JDK serialverto get it, so that it appears you have only one version of the class.

不幸的是,不允许更改字段类型。支持两个(十个、一百个?)不同的版本会费力。所以你可以使用该readObject(ObjectInputStream in)方法。并设置一个固定的serialVersionUID. 如果您最初没有设置它,请使用您的 IDE 或 JDKserialver来获取它,这样看起来您只有一个版本的类。

If you want to change the type of a field, change its name as well. For example paws> pawsCount. The deserialization mechanism doesn't even get to the readObject(..)method if there is a type mismatch in the fields.

如果要更改字段的类型,请同时更改其名称。例如paws> pawsCountreadObject(..)如果字段中存在类型不匹配,反序列化机制甚至不会到达该方法。

For the above example, a working solution would be:

对于上面的例子,一个可行的解决方案是:

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;
    }
}

The fields that were added later will be set to their default values.

稍后添加的字段将设置为其默认值。

Btw, it might be a bit easier to use java.beans.XMLEncoder(if it is not too late for your project)

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

回答by Juliet

Should I do deserialization in one class loader, and if that fails try using another class loader that uses an older version (and so on), or are there better ways?

What's the best strategy?

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

最好的策略是什么?

Serialization really shouldn't be used for long term storage.

序列化真的不应该用于长期存储。

The best strategy here is to make use of a database instead: store your objects in a Petstable, then as you change fields on your table, all of your old data gets updated too, every object has the same and most up-to-date schema.

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

This is really the best way to maintain data for longterm storage, and updates to your old objects to fill in null fields is really easy.

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

回答by Baski

You do not have to maintain multiple version of the class. The latest version should be sufficient. See the link 5 things you don't know about Serializationspecifically "Refactoring Serialized Class"

您不必维护类的多个版本。最新版本应该足够了。请参阅链接您不知道的有关序列化的 5 件事,特别是“重构序列化类”