开发者

Howto deserialize class, if class version was changed

开发者 https://www.devze.com 2023-03-26 20:22 出处:网络
I want to write the code, that can deserialize the class even if the class was changed (but you have old class version).

I want to write the code, that can deserialize the class even if the class was changed (but you have old class version).

The idea is simple.

  1. Read the serialVersionUID of the serialized class
  2. Check if that serialVersionUID is equal to the serialVersionUID of the current class version.
  3. If not, create new ClassLoader and load the old class version int开发者_运维知识库o workspace.

I thought to use something like this:

FileInputStream file = new FileInputStream(filename);
ObjectInputStream o = new ObjectInputStream(file) {
    protected Class<?> resolveClass(java.io.ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        //5639490496474438904L is the suid of the older version
        if (desc.getSerialVersionUID() == 5639490496474438904L) {
            Class c01 = null;
            URL eineURL = new URL("file://localhost/U:/MyJavaProj/bin/test/oldversion/");
            URL[] reiURL = new URL[] {eineURL};
            URLClassLoader clazLader = new URLClassLoader(reiURL);
            c01 = clazLader.loadClass("test.SerObj");
            return c01; 
        }
        return super.resolveClass(desc);
    }
};

SerObj obj = (SerObj) o.readObject();

The problem is in the last line. My current class version is placed in U:/MyJavaProj/bin/test/SerObj.class My old class version is placed in U:/MyJavaProj/bin/test/oldversion/test/SerObj.class

In the last line I read the old class version but cast it to the new version :(

Has anyone some idea or maby any other aproach to add the versioning support for the serialization in Java?


i don't know if you trying to fix an existing problem, or write new functionality. if the latter, then i would look into using the advanced functionality of java serialization. java serialization supports various facilities for being able to handle multiple serial versions of the same class within one classloader (critically, though, you need to keep the serialVersionUID the same for all instances).

here's an example for handling an "incompatible" change, where an integer value was changed to a String value:

public class MyClass {
  private static final int CUR_VERSION = 5;
  private String _value;

  private void readObject(ObjectInputStream in) {
    // first value is always the serial version:
    int dataVersion = in.readInt();
    if(dataVersion == CUR_VERSION) {
      // _value is a String
      _value = in.readString();
    } else {
      // in older versions, _value was an int
      _value = String.valueOf(in.readInt());
    }
  }

  private void writeObject(ObjectOutputStream out) {
    // always write version first
    out.writeInt(CUR_VERSION);
    out.writeString(_value);
  }
}


The simple answer to this is don't change the serialVersionUID.

If you think you've made a serialization-incompatible change to the class, first read the Object Versioning part of the Object Serialization Specification to double-check that, as you probably haven't, and if you really have, change/add the writeObject/readObject methods so that they can cope with the old serialization format.

And certainly don't try to mess around with two versions of the class at runtime. It won't work.


You won't be able to perform the cast in the last line, because it's of a different class (which is not castable) - you might get a confusing message such as test.SerObj is not an instance of test.SerObj.

This arises from the fact that a class is essentially a java.lang.Class instance, which critically is unique within a given classloader. Even if you referenced the exact same *.class file, an instance of SerObj loaded by the default classloader is a different class from an instance of SerObj loaded by clazLader. Neither class could be cast to the other one.

Thus it is not going to be possible for the method to return SerObj if you need to use multiple classloaders to deserialise. (Besides - how could it, when the two class files could have arbitrary differences?)


One possible workaround is to define an interface that defines the behaviour which is fixed between versions of the class. If you set up the temporary classloader such that it can only load SerObj from the oldversion class file, and that it has the right parent classloader, then your old SerObj should still implement the interface class as defined by the root classloader.

In this was, both the new SerObj and old SerObj instances would be castable to the SerObjIface interface, and so you could declare your method to return that. And in fact if you want callers to deal with different yet similar classes (which is what different versions of "the same class" are), you should arguably be returning an interface for this anyway.

0

精彩评论

暂无评论...
验证码 换一张
取 消