简体   繁体   中英

Java de-serialization backward compatibility

How can I deserialize a class which was modified after serialization?

More specifically, I know this can be done when a class had serialVersionUID in its initial version. Is there any way to do it for classes without serialVersionUID ?

I have an object

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

}

and I serialize classes like this

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

    }
}

and deserialize like this

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

    }
}

After some time, I added

@Override
public String toString() {
    return "MyObject [name=" + name + ", names=" + names + "]";
}

to MyObject . After adding that I got exceptions like

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)     

Please help me with this.

Thanks @Gábor Bakos.

This can be solved by creating serialVersionUID for older class (Which signatures should be same as the one during serialization )and adding that serialVersionUID in current class.

serialver -classpath /***PATH***/bin com.test.serialize.MyObject

That returns

com.test.serialize.MyObject:    static final long serialVersionUID = 5512234731442983181L;

After that I have added it to my MyObject as below

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 + "]";
}

}

It works fine.

More info refer: serialver

First advice: use serialization, because everything is almost done.

Second advice: use a serialVersionUID and keep it fix with one version: it is here to warn you and prevent confusion between different serialized versions.

So : if you change fields or meaning of fields, change the serialVersionUID.

Then you have your backward compatibility problem.

See this for many ideas: Managing several versions of serialized Java objects

IMHO:

  • whatever solution you take, keep in mind that your program will be managing objects with partial datas: then you have to manage all cases with or without datas.

  • if you dont change often your version: use several different classes. Perhaps subclasses, or implementations of an interface: then you can get your program, and you manage several versions of object: MyClass_V1, MyClass_V2, etc. When you deserialize, you can test/retry and get the good Object. After that, you perhaps have to convert datas between your classes

  • if you change your version, by adding new fields (not changing old fields), it is a little more easy (subclasses, converting is direct to parents)

  • or you could consider use a XML structure to serialize and deserialize: you can have backward and forward compatibility because it is extensible : fields are there, or are null. You have to manage mapping yourself or use some libraries.

Hope it helps !

I would remember following points,

  1. Every Serializable class contains a serialVersionUID ( it doesn't matter if you have specified the one explicitly or not ).
  2. There are compatible changes and there are incompatible changes
    eg adding a new field is a compatible change, removing a field is not a compatible change. Adding / removing / editing a method are generally compatible changes but in your case surely that is not the way it is ( serialVersionUID got changed after you added toString() method)

3.Prior to modify the class, you can use serialver utility to find serialVersionUID of old class and use that in new class

Don't think there are any other magic tricks :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM