8

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

我的情况是;

我知道旧的serialVersionUID 和新的serialVersionUID。

在读取具有旧 serialVersionUID 的对象时,我想操作数据以使其适合新版本,如果我正在读取的对象是旧类型,我只想费心去做。

这似乎应该是非常直截了当的事情!

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

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

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

感谢所有帮助!

4

3 回答 3

12

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

使用迁移器

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

  1. 统一所有这些类的不可变接口
  2. 该接口的旧实现注释为@Deprecated
  3. 你的接口的新实现
  4. 一个 Migrator 类,可帮助您将已弃用的对象转换为新对象

让我从一开始就说,并不总是可以使用旧版本的对象(请参阅有关可序列化对象的版本控制的 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二进制文件(例如使用. 这是一个例子:ObjectOutputStreamEntity_newEntityEntity_new

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

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

设计一个通用的可序列化接口

如果适用,您可以首先尝试为您的类设计一个未来不太可能更改的接口。如果您的类需要存储一组很可能被修改的属性,请考虑Map<String, Object>为此目的使用 a,其中String指的是属性名称/标识符,并且Object是对应的值。

自定义 readObject 和 writeObject

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

替代序列化技术

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

于 2011-02-15T15:52:27.823 回答
6

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

于 2011-02-15T15:32:48.817 回答
1

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

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

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

于 2011-02-15T15:33:53.267 回答