如果类版本已更改,如何反序列化类

时间:2011-08-09 16:38:08

标签: java serialization versioning classloader

我想编写代码,即使类被更改(但你有旧的类版本),也可以反序列化类。

这个想法很简单。

  1. 阅读序列化课程的serialVersionUID
  2. 检查serialVersionUID是否等于当前班级版本的serialVersionUID
  3. 如果没有,请创建新的ClassLoader并将旧的类版本加载到工作区中。
  4. 我想用这样的东西:

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

    问题出在最后一行。我当前的班级版本位于 U:/MyJavaProj/bin/test/SerObj.class 我的旧班级版本放在 U:/MyJavaProj/bin/test/oldversion/test/SerObj.class

    在最后一行中,我阅读了旧的类版本,但将其转换为新版本:(

    有没有人有想法或maby任何其他方法为Java中的序列化添加版本控制支持?

3 个答案:

答案 0 :(得分:3)

我不知道您是否尝试修复现有问题或编写新功能。如果是后者,那么我会研究使用java序列化的高级功能。 java序列化支持各种工具,可以在一个类加载器中处理同一个类的多个串行版本(但是,关键是,你需要保持serialVersionUID与所有实例的相同)。

这是处理“不兼容”更改的示例,其中整数值更改为String值:

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

答案 1 :(得分:2)

对此的简单回答是不要更改serialVersionUID

如果您认为自己对类进行了序列化不兼容的更改,请首先阅读对象序列化规范的Object Versioning部分,仔细检查一下,如您可能没有,如果您真的拥有,更改/添加writeObject / readObject方法,以便他们可以应对旧的序列化格式。

当然不要试图在运行时使用该类的两个版本。它不起作用。

答案 2 :(得分:1)

你将无法在最后一行执行强制转换,因为它是一个不同的类(不可转换) - 你可能会收到一个令人困惑的消息,例如test.SerObj is not an instance of test.SerObj

这是因为一个类本质上是一个java.lang.Class实例,其中在给定的类加载器中是唯一的。即使您引用了完全相同的*.class文件,默认类加载器加载的SerObj实例也与SerObj加载的clazLader实例不同。这两个课程都不能投入到另一个课程中。

因此,如果需要使用多个类加载器进行反序列化,则该方法无法返回SerObj。 (除此之外 - 当两个类文件有任意差异时,怎么可能呢?)


一种可能的解决方法是定义一个接口,该接口定义在类的版本之间修复的行为。如果您设置临时类加载器,使其从oldversion类文件中加载SerObj,并且它具有正确的父类加载器,那么您的旧SerObj仍应实现根类加载器定义的接口类。

在这种情况下,新的SerObj和旧SerObj实例都可以转换为SerObjIface接口,因此您可以声明您的方法返回那个。事实上,如果你想让调用者处理不同但类似的类(这是“同一类”的不同版本),你应该可以为此返回一个接口。