升级 Java 可序列化类
我读过各种关于序列化和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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我将介绍几种可能的方法来支持序列化中旧版本的类/接口:
使用迁移器
在这种情况下,您的 Java 项目中需要以下内容:
让我从一开始就说它不是始终可以使用旧版本的对象(请参阅 Oracle 文档 可序列化对象的版本控制)。为了简单起见,我们假设在升级时,您的类始终实现您定义的接口
IEntity
。并假设您最初使用该类:
如果您需要将类
Entity
升级为具有新serialVersionUID的新实现,则首先将其注释为@Deprecated
但< em>不要重命名它并且不要将其移动到另一个包:现在使用新的serialVersionUID(重要)和附加构造函数创建新的实现,如下所示
...当然整个过程中最关键的部分是如何实现上述构造函数。如果您已将旧类型
Entity
的一些对象序列化为二进制文件(例如使用ObjectOutputStream
),并且现在已迁移到Entity_new
,您可以将它们解析为 Entity 的实例,然后将它们转换为 Entity_new 的实例。这是一个示例:当然还有其他替代方案,不需要特定的构造函数或使用 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:
@Deprecated
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.And assume that you initially work with the class:
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 :Now create your new implementation with a new serialVersionUID (important) and an additional constructor as follows...
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 exampleObjectOutputStream
) and you have now migrated toEntity_new
you can parse them as instances ofEntity
and then convert them into instances ofEntity_new
. Here is an example: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, whereString
refers to the property name/identifier andObject
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)
andprivate 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.
您应该保持相同的
serialVersionUID
。序列化字段不必与类本身的字段匹配。使用ObjectInputStream.readFields
并定义serialPercientFields
(尽管请确保拼写正确)。You should keep the same
serialVersionUID
. The serialised fields don't have to match those of the class itself. UseObjectInputStream.readFields
and define aserialPersistentFields
(although do make sure you spell that correctly).这不是一件容易的事。如果您的代码可以支持这两种类类型,则处理此问题的最佳方法是不更改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).