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

2022-09-02 21:26:17

我有一个 Java 类,它存储在一个 HttpSession 对象中,该对象被序列化并在集群环境中的服务器之间传输。出于本解释的目的,让我们将此类称为“Person”。

在改进代码的过程中,此类已从“com.acme.Person”移至“com.acme.entity.Person”。在内部,类保持完全相同(相同的字段,相同的方法,相同的一切)。

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

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


答案 1

我发现这篇博客文章声称有一个解决方案,尽管它没有非常清楚地说明。

它实际上是在说,你创建了一个子类,该子类覆盖了该方法以执行如下操作:ObjectInputStreamreadClassDescriptor

@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问题及其答案,这些答案涵盖了与您的问题相同的一些基础。

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

  • 这是一个很好的机会,可以鼓励(实际上强制)人们升级到最新版本的代码库。一般来说,这早日发生符合每个人的利益。

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


答案 2

这始终是 Java 序列化的一大麻烦。只要您要迁移类,我就建议您考虑迁移到不同的序列化机制,如 XStream。在JavaLobby上有一篇关于这个问题的有趣文章。