升级 Java 可序列化类

发布于 2024-10-17 15:14:16 字数 458 浏览 2 评论 0原文

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

我的情况是;

我知道旧的serialVersionUID和新的serialVersionUID。

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

这看起来应该是非常简单的事情!

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

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

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

非常感谢所有帮助!

中号

I have read various blogs about Serialization and the use of serialVersionUID. Most of them mention using it to maintain the state of a serializable class.

The scenario I have is;

I know the old serialVersionUID and the new serialVersionUID.

On reading in an object with the old serialVersionUID I want to manipulate the data so it fits the new version, I only want to bother to do this if the object I am reading in is of the old type.

This seems like something that should be very straight forwards!

Is there a way to get hold of the serialVersionUID as the object is read in?

The InvalidClassException is thrown before the readObject method in the serialized class is invoked so I can't access it there.

The only hint I have found is to override the ObjectInputStream so that readClassDescriptor() is available although this seems a heavy weight solution to what must be a common problem!

All help is gratefully received!

M

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

久随 2024-10-24 15:14:16

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

使用迁移器

在这种情况下,您的 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但< em>不要重命名它并且不要将其移动到另一个包

@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 反射。还有许多其他设计方法可供选择。另请注意,为了简单起见,上述代码中完全省略了异常处理和空对象检查。

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

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

自定义 readObject 和 writeObject

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

替代序列化技术

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

I am going to present a few possible ways to support older versions of a class/interface in serialization:

Use Migrators

In such a case you need the following in your Java project:

  1. An immutable interface that unifies all these classes
  2. The old implementation(s) of that interface annotated as @Deprecated
  3. The new implementation of your interface
  4. A Migrator class that helps you convert a deprecated object to a new one

Let me say from the beginning that it's not always possible to work with objects of older versions (see the Oracle documentation on versioning of serializable objects). For the sake of simplicity, let us assume that while upgrading, your classes always implement the interface IEntity that you have defined.

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

And assume that you initially work with the class:

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

If you need to upgrade the class Entity into a new implementation with a new serialVersionUID, then first annotate it as @Deprecated but don't rename it and don't move it to another package :

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

Now create your new implementation with a new serialVersionUID (important) and an additional constructor as follows...

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
  }

}

Of course the most critical part of the whole procedure is how you implement the above constructor. If you have serialized some objects of the old type Entity as binary files (using for example ObjectOutputStream) and you have now migrated to Entity_new you can parse them as instances of Entity and then convert them into instances of Entity_new. Here is an example:

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

There are of course other alternative that don't require that particular constructor or use java reflection. There are lots of other design approaches one can choose. Note also that exception handling and checks for null objects have been completely omitted for the sake of simplicity in the above code.

Design a generic serializable interface

If applicable, you can try in the first place to design an interface for your class that is not likely to change in the future. If your class needs to store a set of properties that is very likely to be modified, consider using a Map<String, Object> for this purpose, where String refers to the property name/identifier and Object is the corresponding value.

Customize readObject and writeObject

There is yet another way to provide support for older version which I will mention for completeness, but is not one I would choose. You can implement private void readObject(ObjectInputStream in) and private void writeObject(ObjectOutputStream out) in a way that it accommodates both the current and all previous versions of your class/interface. I am not sure whether something like this is always feasible and sustainable and you may end up having a very messy and lengthy implementation of these methods.

Alternative serialization techniques

This doesn't answer the OP's question, but I reckon it is worth bringing it up. You may consider serializing your object in some ASCII format such as JSON, YAML or XML. In such a case, unless you vehemently redesign your serializable interface, extensibility comes out of the box. BSON (binary JSON) is a good choice if you are looking for an extensible binary protocol. Maybe this is the best way to go to provide portability of your objects across software that may not be implemented in Java.

·深蓝 2024-10-24 15:14:16

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

You should keep the same serialVersionUID. The serialised fields don't have to match those of the class itself. Use ObjectInputStream.readFields and define a serialPersistentFields (although do make sure you spell that correctly).

梦在深巷 2024-10-24 15:14:16

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

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

简而言之,更改serialVersionUID不是维护状态的方法,而是指示不兼容的方法(即,退出是唯一解决方案的情况)。

This is not an easy thing to do. if your code can support both class types, the best way to handle this is to not change the serialVersionUID, but instead detect whether the data is new or old and read the data accordingly.

if you want to do a one time upgrade of old data to new, you need to setup some sort of class juggling where the old class and new class are both available to the process (like, separate classloaders). you need to read the data using the old class, copy to new and re-write. this is definitely not the best way to do things, though.

in short, changing the serialVersionUID is not the way to maintain state, it is the way to indicate incompatibility (i.e. a situation where bailing out is the only solution).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文