【问题标题】:Howto deserialize class, if class version was changed如果更改了类版本,如何反序列化类
【发布时间】:2011-08-09 16:38:07
【问题描述】:

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

这个想法很简单。

  1. 读取序列化类的serialVersionUID
  2. 检查serialVersionUID 是否等于当前类版本的serialVersionUID
  3. 如果没有,请创建新的ClassLoader 并将旧的类版本加载到工作区中。

我想用这样的东西:

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

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

有没有人知道或可能有其他方法来为 Java 中的序列化添加版本控制支持?

【问题讨论】:

  • 究竟是什么问题 - 你能提供异常堆栈跟踪吗?
  • 线程“main”java.lang.ClassCastException 中的异常:test.SerObj 无法转换为 test.SerObj :)

标签: java serialization versioning classloader


【解决方案1】:

我不知道您是尝试解决现有问题还是编写新功能。如果是后者,那么我会考虑使用 java 序列化的高级功能。 java 序列化支持各种工具,以便能够在一个类加载器中处理同一类的多个序列版本(但关键的是,您需要保持所有实例的 serialVersionUID相同)。

这是一个处理“不兼容”更改的示例,其中一个整数值被更改为一个字符串值:

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

【讨论】:

  • 是的。我知道这个解决方案。但它不会通过一些不兼容的序列化更改来工作。也就是我要支持的东西
  • 不是真的。您当然可以处理不兼容的更改,您只需要在自己的代码中处理转换即可。
  • 很好的例子。但是,我建议将版本硬编码到代码中。而不是“if(dataVersion == CUR_VERSION)”使用“if(dataVersion >= 5)”。否则当你跳转到版本 6 时,它会不正确地反序列化 v5 数据。
  • @ldkronos - 我不明白。如果您更改为版本 6,那么您可能会更改代码...
  • @jtahlborn - 虽然这个例子很简单,但实际上您可能会反序列化流中的多个字段。虽然版本 5 可能影响了第三个变量的处理,但版本 6 可能涉及到第七个变量的处理。示例的编写方式,当您在版本 6 中添加对变量 #7 的处理时,您首先必须返回并为版本 >= 5 重写变量 #3 的处理。因此,不要让您记住以后更改它(并且可能会破坏它),现在就正确地执行它,那么将来不会有任何破坏。
【解决方案2】:

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

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

当然不要试图在运行时弄乱类的两个版本。它不会工作。

【讨论】:

    【解决方案3】:

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

    这是因为一个类本质上是一个java.lang.Class 实例,它在给定的类加载器中至关重要地是唯一的。即使您引用了完全相同的*.class 文件,默认类加载器加载的SerObj 实例与clazLader 加载的SerObj 实例是不同的类。两个类都不能转换为另一个类。

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


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

    在 this was 中,新的SerObj 和旧的SerObj 实例都可以转换为SerObjIface 接口,因此您可以声明您的方法以返回那个。实际上,如果您希望调用者处理不同但相似的类(这是“同一类”的不同版本),那么无论如何您都应该为此返回一个接口。

    【讨论】:

    • 谢谢。我对界面有同样的想法,但很有趣的是,如果我能用另一种方式解决它。
    • 但是看起来,像转换成通用接口或父类是唯一的解决方案。
    • 是的 - 由于类不同,它们只能共享一个父接口或超类(在极端情况下,一直到java.lang.Object)。我认为从概念上讲不可能绕过这个限制。
    • 您可以使用反射从旧类中读取字段并将其设置在新类的实例上。
    猜你喜欢
    • 2016-12-30
    • 2020-08-12
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 2020-03-02
    • 2019-07-01
    • 2020-10-30
    • 1970-01-01
    相关资源
    最近更新 更多