Managing several versions of serialized Java objects

前端 未结 4 689
野趣味
野趣味 2020-11-30 05:44

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 encounter

相关标签:
4条回答
  • 2020-11-30 06:01

    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 Serialization specifically "Refactoring Serialized Class"

    0 讨论(0)
  • 2020-11-30 06:03

    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 Pets table, 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.

    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.

    0 讨论(0)
  • 2020-11-30 06:17

    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 serialver to get it, so that it appears you have only one version of the class.

    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.

    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)

    0 讨论(0)
  • 2020-11-30 06:20

    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 serialVersionUID unless you want InvalidClassException to be thrown. The second rule is to not change the types of 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 sharpTeeth field but the file doesn't then it will get initialized to its default value, false in this case.

    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 serialVersionUID but 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.

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

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

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

    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:

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

    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.

    0 讨论(0)
提交回复
热议问题