升级Java Serializable类

时间:2011-02-15 15:06:59

标签: java serialization objectinputstream

我已阅读各种关于序列化和serialVersionUID使用的博客。他们中的大多数都提到使用它来维护可序列化类的状态。

我的情景是;

我知道旧的serialVersionUID和新的serialVersionUID。

在使用旧的serialVersionUID读取对象时,我想操纵数据以使其适合新版本,如果我正在读取的对象是旧类型,我只想打扰这样做。

这似乎应该是非常直接的东西!

有没有办法在读入对象时获取serialVersionUID?

在调用序列化类中的readObject方法之前抛出InvalidClassException,因此我无法在那里访问它。

我发现的唯一提示是覆盖ObjectInputStream,以便readClassDescriptor()可用,尽管这似乎是一个重要的解决方案,必须是一个常见的问题!

感谢所有帮助!

中号

3 个答案:

答案 0 :(得分:12)

我将介绍几种可能的方法来支持序列化中的旧版本的类/接口:

使用迁移器

在这种情况下,您需要在Java项目中使用以下内容:

  1. 统一所有这些类的不可变接口
  2. 该接口的旧实现,注释为@Deprecated
  3. 界面的新实现
  4. 可帮助您将已弃用的对象转换为新对象的Migrator类
  5. 让我从一开始就说,并不总是可以使用旧版本的对象(参见versioning of serializable objects上的Oracle文档)。为简单起见,我们假设在升级时,您的类始终实现您已定义的接口IEntity

    public interface IEntity extends Serializable {
       // your method definitions
    }
    

    并假设您最初使用该课程:

    public class Entity implements IEntity {
       private static final long serialVersionUID = 123456789L;
       // fields and methods
    }
    

    如果您需要将类Entity升级为具有新serialVersionUID的新实现,请先将其注释为@Deprecated,但不要重命名不要将其移动到另一个包

    @Deprecated 
    public class Entity implements IEntity {
    private static final long serialVersionUID = 123456789L;
      // fields and methods
    }
    

    现在使用新的serialVersionUID(重要)和一个额外的构造函数创建新的实现,如下所示......

    public class Entity_new implements IEntity {
      private static final long serialVersionUID = 5555558L;
    
      public Entity_new(IEntity){
         // Create a new instance of Entity_new copying the given IEntity
      }
    
    }
    

    当然,整个过程中最关键的部分是如何实现上述构造函数。如果您已将旧类型Entity的某些对象序列化为二进制文件(例如使用ObjectOutputStream)并且您现在已迁移到Entity_new,则可以将它们解析为{{1}的实例然后将它们转换为Entity的实例。这是一个例子:

    Entity_new

    当然,其他替代方案不需要特定的构造函数或使用java reflection。有许多其他设计方法可供选择。另请注意,为了简化上述代码,已完全省略异常处理和空对象检查。

    设计通用的可序列化界面

    如果适用,您可以首先尝试为您的班级设计一个不太可能在将来更改的界面。如果您的类需要存储一组很可能被修改的属性,请考虑使用public class Migrator { private final IEntity entity; private Class<? extends IEntity> newestClass = Entity_new.class; public Migrator(final IEntity entity){ this.entity = entity; } public Migrator setNewestClass(Class<? extends IEntity> clazz){ this.newestClass = clazz; return this; } public IEntity migrate() throws Exception { Constructor<? extends IEntity> constr = newestClass.getConstructor(IEntity.class); return constr.newInstance(this.entity); } } 来实现此目的,其中Map<String, Object>引用属性名称/标识符,String是相应的值。

    自定义readObject和writeObject

    还有另一种方法可以为旧版本提供支持,我将在完整性方面提及,但不是我会选择的。您可以通过适应类/接口的当前版本和所有先前版本的方式实现Objectprivate void readObject(ObjectInputStream in)。我不确定这样的事情是否总是可行和可持续的,你可能最终会对这些方法进行非常混乱和冗长的实施。

    替代序列化技术

    这不符合OP的问题,但我认为值得提出。您可以考虑以某种ASCII格式(如JSON,YAML或XML)序列化对象。在这种情况下,除非您激烈地重新设计可序列化的界面,否则可扩展性是开箱即用的。如果您正在寻找可扩展的二进制协议,BSON(二进制JSON)是一个不错的选择。也许这是在可能无法用Java实现的软件中提供对象可移植性的最佳方式。

答案 1 :(得分:6)

你应该保持相同的serialVersionUID。序列化字段不必与类本身的字段匹配。使用ObjectInputStream.readFields并定义serialPersistentFields(尽管确保拼写正确)。

答案 2 :(得分:1)

这不是一件容易的事。如果您的代码可以同时支持两种类型,那么处理此类代码的最佳方法是更改serialVersionUID,而是检测数据是新的还是旧的并相应地读取数据。

如果你想将旧数据一次性升级到新数据,你需要设置某种类杂耍,其中旧类和新类都可用于进程(例如,单独的类加载器)。你需要使用旧类读取数据,复制到新的并重写。但这绝对不是最好的做事方式。

简而言之,更改serialVersionUID不是维护状态的方式,而是指示不兼容的方式(即拯救是唯一的解决方案)。