java - 升级 Java Serializable 类

标签 java serialization objectinputstream

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

我的场景是;

我知道旧的 serialVersionUID 和新的 serialVersionUID。

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

这看起来应该是非常直接的事情!

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

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

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

感谢所有帮助!

中号

最佳答案

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

使用迁移器

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

  1. 统一所有这些类的不可变接口(interface)
  2. 该接口(interface)的旧实现被注释为 @Deprecated
  3. 界面的新实现
  4. 一个迁移器类,可帮助您将已弃用的对象转换为新的对象

让我从一开始就说,并不总是可以使用旧版本的对象(请参阅 versioning of serializable objects 上的 Oracle 文档)。为了简单起见,让我们假设在升级时,您的类总是实现您定义的接口(interface) 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 您可以将它们解析为 Entity 的实例,然后将它们转换为 Entity_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 reflection 。还有许多其他设计方法可供选择。另请注意,为简化上述代码,已完全省略异常处理和空对象检查。

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

如果适用,您可以首先尝试为您的类设计一个将来不太可能更改的接口(interface)。如果你的类需要存储一组极有可能被修改的属性,可以考虑为此使用一个Map<String, Object>,其中String指的是属性名/标识符,Object是对应的值。

自定义 readObject 和 writeObject

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

替代序列化技术

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

关于java - 升级 Java Serializable 类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5005330/

相关文章:

来自 WCF 的 JSON 响应转义名称值

java - Android 应用程序在从 java.lang.NullPointerException 读取对象时启动时崩溃

java - 如何告诉 cxf 将包装器类型保留在方法中?

java - Blowfish 在 Java/Scala 中加密并在 bash 中解密

java - 如何在android中每1分钟移动一次获得不同的纬度和经度?

java - XML 解析错误 : org. xml.sax.SAXParseException

c# - 为什么二进制序列化需要将对象标记为可序列化?

java - 通过ObjectInputStream接收对象(Java)

java - 安卓/java : use something else rather then ObjectInputStream to load an object

java - DB2 Java 用户定义函数