如何处理包更改的Java序列化对象?

时间:2011-03-14 22:38:43

标签: java serialization versioning

我有一个存储在HttpSession对象中的Java类,该对象在集群环境中的服务器之间进行序列化和转换。出于此解释的目的,我们将此类称为“Person”。

在改进代码的过程中,这个类从“com.acme.Person”转移到“com.acme.entity.Person”。在内部,类保持完全相同(相同的字段,相同的方法,相同的所有内容)。

问题是我们有两组服务器同时运行旧代码和新代码。 具有旧代码的服务器具有序列化的HttpSession对象,并且当新代码反序列化它时,它会抛出ClassNotFoundException,因为它找不到对com.acme.Person的旧引用。此时,处理这个很容易,因为我们可以使用新包重新创建对象。然后问题变成新服务器中的HttpSession将使用对com.acme.entity.Person的新引用来序列化对象,并且当在运行旧代码的服务器中对其进行反序列化时,将抛出另一个异常。在这一点上,我们不能再处理这个例外了。

此类案件的最佳策略是什么? 有没有办法告诉新服务器通过引用旧包序列化对象并将旧包的引用反序列化为新包? 所有服务器运行新代码后,我们将如何过渡到使用新包并忘记旧包?

4 个答案:

答案 0 :(得分:16)

我发现这个blog post声称有一个解决方案,但它并没有非常明确地解释它。

它实际上是说你创建了一个ObjectInputStream的子类来覆盖readClassDescriptor方法来做这样的事情:

@Override
protected java.io.ObjectStreamClass readClassDescriptor() 
        throws IOException, ClassNotFoundException {
    ObjectStreamClass desc = super.readClassDescriptor();
    if (desc.getName().equals("oldpkg.Widget")) {
        return ObjectStreamClass.lookup(newpkg.Widget.class);
    }
    return desc;
};

您还应该看一下SO question and its answers,其中包含与您的问题相同的一些内容。

我的建议是:不支持支持旧版本软件读取新版本序列化数据的情况。

  • 这是一个鼓励(实际上强迫)人们升级到最新版本的代码库的好机会。一般来说,这是每个人的利益,这种情况发生的时间越早越好。

  • 如果由于其他原因迫使人们升级为时过早,那么(IMO)您应该认真考虑退出您对类/包名称的更改。等到你有一个明确的升级策略/计划,1)技术上合理,2)所有利益相关者都可以接受。

答案 1 :(得分:3)

这一直是Java序列化的一大难题。只要你正在迁移你的类,我建议你考虑迁移到不同的序列化机制,比如XStream。 JavaLobby有一个有趣的article on this

答案 2 :(得分:2)

如果您愿意宣传您的更改,那么您已经搞砸了,反弹旧服务器。任何实现目标的黑客行为都会增加更多的垃圾代码,并造成更多混乱和麻烦。

答案 3 :(得分:2)

在某些情况下,您无权访问ObjectInputStream,也无法覆盖readClassDescriptor()。例如,另一个子系统序列化和反序列化这些对象。在我们的例子中,我们在Quartz作业数据映射中序列化了旧实例。

对于这种情况,您必须维护旧类的浅层定义。您可以实现旧类的readResolve()writeReplace()方法。

class OldClass{
  private int aField;
  private Object readResolve() throws ObjectStreamException {
     return new NewClass(aField);
  }
  private Object writeReplace() throws ObjectStreamException {
     return new NewClass(aField);
  }
}