java 当对象具有不同的 serialVersionUID 时,如何反序列化现在保存在数据库中的对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/795470/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to deserialize an object persisted in a db now when the object has different serialVersionUID
提问by Jorge Perez
My client has an oracle data base and an object was persisted as a blob field via objOutStream.writeObject, the object now has a different serialVersionUID(even though the object has no change, maybe different jvm version) and when they try to de-serialize an exception is thrown:
我的客户有一个 oracle 数据库,并且一个对象通过 objOutStream.writeObject 作为 blob 字段持久化,该对象现在有一个不同的serialVersionUID(即使该对象没有变化,可能是不同的 jvm 版本)并且当他们尝试反序列化一个抛出异常:
java.io.InvalidClassException: CommissionResult; local class incompatible:
stream classdesc serialVersionUID = 8452040881660460728,
local class serialVersionUID = -5239021592691549158
They didn't assign a fixed value for serialVersionUIDsince the beginning so now that some thing changed that exception is thrown. Now they don't want to loose any data, to do so I think the best is to read the objects, de-serialize them, and persist them again via XMLEncoder to avoid future errors like the current "class incompatible" error.
他们serialVersionUID从一开始就没有为其分配固定值,所以现在有些事情发生了变化,抛出了异常。现在他们不想丢失任何数据,为此我认为最好的方法是读取对象,反序列化它们,然后通过 XMLEncoder 再次持久化它们,以避免将来出现诸如当前“类不兼容”错误之类的错误。
Apparently there are 2 different values for the serialVersionUIDpersisted for that object so I want to read the data, try with one value and if it fails then try with the other value, To do so I've tried to change the serialVersionUIDof the class using
the ASM api. I've been able to change the value but the problem is how to make active the change upon the class so when it is de-serialized the objInpStr.readObject()take my modified version of the class with my specific serializedVersionUID. I made a test class to simulate the real environment, I take an object (which has as property the object with different serialVersionUIDproblem) the object name is Reservationthe property is
CommissionResult:
显然有对2个不同的值serialVersionUID持续了那个对象,所以我想读取数据,尝试用一个值,如果失败则与其他值尝试,要做到这一点我已经试过改变serialVersionUID类使用
的ASM API。我已经能够更改该值,但问题是如何在类上激活更改,以便在反序列化时objInpStr.readObject()使用我的特定serializedVersionUID. 我做了一个测试类来模拟真实环境,我取一个对象(它具有不同serialVersionUID问题的对象作为属性)对象名称是Reservation属性是
CommissionResult:
public class Reservation implements java.io.Serializable {
private CommissionResult commissionResult = null;
}
public class CommissionResult implements java.io.Serializable{
}
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.SerialVersionUIDAdder;
public class SerialVersionUIDRedefiner extends ClassLoader {
public void workWithFiles() {
try {
Reservation res = new Reservation();
FileOutputStream f = new FileOutputStream("/home/xabstract/tempo/res.ser");
ObjectOutputStream out = new ObjectOutputStream(f);
out.writeObject(res);
out.flush();
out.close();
ClassWriter cw = new ClassWriter(0);
ClassVisitor sv = new SerialVersionUIDAdder(cw); //assigns a real serialVersionUID
ClassVisitor ca = new MyOwnClassAdapter(sv); //asigns my specific serialVerionUID value
ClassReader cr=new ClassReader("Reservation");
cr.accept(ca, 0);
SerialVersionUIDRedefiner loader= new SerialVersionUIDRedefiner();
byte[] code = cw.toByteArray();
Class exampleClass = loader.defineClass("Reservation", code, 0, code.length); //at this point the class Reservation has an especific serialVersionUID value that I put with MyOwnClassAdapter
loader.resolveClass(exampleClass);
loader.loadClass("Reservation");
DeserializerThread dt=new DeserializerThread();
dt.setContextClassLoader(loader);
dt.run();
} catch (Exception e) {
e.printStackTrace();
}}
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializerThread extends Thread {
public void run() {
try {
FileInputStream f2;
f2 = new FileInputStream("/home/xabstract/tempo/res.ser");
ObjectInputStream in = new ObjectInputStream(f2);
Reservation c1 = (Reservation)in.readObject();
System.out.println(c1);
} catch (Exception e) {
e.printStackTrace();
}
stop();
}
}
MyOwnClassAdapter Relevant code:
public void visitEnd() {
// asign SVUID and add it to the class
try {
cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
"serialVersionUID",
"J",
null,
new Long(-11001));//computeSVUID()));
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException("Error while computing SVUID for x"
, e);
}
super.visitEnd();
}
The test should fail with the java.io.InvalidClassException"local class incompatible"
because I changed the serialVersionUIDafter I saved the file and used a new one to read
de file but it doesn't fails so it means that the ObjectInputStream.readObjectis not
using my modified version of the Reservationclass.
测试应该会因java.io.InvalidClassException“本地类不兼容”而失败,因为我serialVersionUID在保存文件并使用新文件读取 de 文件后更改了它,但它没有失败,所以这意味着它ObjectInputStream.readObject没有使用我修改过的Reservation类版本。
Any Ideas? Thanks in advance.
有任何想法吗?提前致谢。
!!!!!!!!!!!!!UPDATE:
!!!!!!!!!!!!!更新:
Ok, it is possible to redefine the resultClassDescriptor to override the stream serialVersionUID, but, some thing strange happens, as I said before it seems there are 2 versions of the class persisted, objects with serialVersionUID = -5239021592691549158L and others with value 8452040881660460728L this last value is the one generated if I don't specify a value to the local class.
好的,可以重新定义 resultClassDescriptor 来覆盖流 serialVersionUID,但是,发生了一些奇怪的事情,正如我之前所说,似乎有 2 个版本的类持久化,serialVersionUID = -5239021592691549158L 的对象和其他值为 845204088166804607 的对象value 是我没有为本地类指定值时生成的值。
-If I don't specify a value for the serialVersionUID then the default value (8452040881660460728L) is used, but is not possible to de-serealize the objects that has the other value, an error is thrown saying that a property is of an other type.
- 如果我没有为 serialVersionUID 指定值,则使用默认值 (8452040881660460728L),但无法对具有其他值的对象进行反序列化,则会引发错误,指出属性属于其他值类型。
-If I specify the value -5239021592691549158L then classes persisted with that value are successfully de-serialized, but not the others, same error of types.
-如果我指定值 -5239021592691549158L,则使用该值持久化的类将成功反序列化,但其他类不会,类型相同的错误。
this is the error trace :
这是错误跟踪:
Potentially Fatal Deserialization Operation. java.io.InvalidClassException: Overriding serialized class version mismatch: local serialVersionUID = -5239021592691549158 stream serialVersionUID = 8452040881660460728 java.lang.ClassCastException: cannot assign instance of java.util.HashMap to field com.posadas.ic.rules.common.commisionRules.CommissionResult.statusCode of type java.lang.String in instance of com.posadas.ic.rules.common.commisionRules.CommissionResult
潜在的致命反序列化操作。java.io.InvalidClassException:覆盖序列化类版本不匹配:本地 serialVersionUID = -5239021592691549158 流 serialVersionUID = 8452040881660460728 java.lang.ClassCastException:无法将 java.util.HashMap 的实例分配给字段 common.com.composionR com.posadas.ic.rules.common.commisionRules.CommissionResult 实例中 java.lang.String 类型的 CommissionResult.statusCode
When this error was thrown the class had the value of -5239021592691549158, if change the value to 8452040881660460728 the class is successfully de-serialized, so, what happens? why is that error that tries to cast for wrong class ?
抛出此错误时,该类的值为 -5239021592691549158,如果将该值更改为 8452040881660460728,该类将成功反序列化,那么会发生什么?为什么那个试图为错误的类进行转换的错误?
Thanks
谢谢
回答by Bhushan Bhangale
Jorge I found one solution on http://forums.sun.com/thread.jspa?threadID=518416which works.
Jorge 我在http://forums.sun.com/thread.jspa?threadID=518416上找到了一个有效的解决方案。
Create the below class in your project. Whereever you creating object of ObjectInputStream, use DecompressibleInputStream instead and it deserializes the old object with the new version Id class.
在您的项目中创建以下类。无论您在何处创建 ObjectInputStream 对象,都应改用 DecompressibleInputStream 并使用新版本 Id 类反序列化旧对象。
public class DecompressibleInputStream extends ObjectInputStream {
public DecompressibleInputStream(InputStream in) throws IOException {
super(in);
}
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor
Class localClass = Class.forName(resultClassDescriptor.getName()); // the class in the local JVM that this descriptor represents.
if (localClass == null) {
System.out.println("No local class for " + resultClassDescriptor.getName());
return resultClassDescriptor;
}
ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
if (localClassDescriptor != null) { // only if class implements serializable
final long localSUID = localClassDescriptor.getSerialVersionUID();
final long streamSUID = resultClassDescriptor.getSerialVersionUID();
if (streamSUID != localSUID) { // check for serialVersionUID mismatch.
final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: ");
s.append("local serialVersionUID = ").append(localSUID);
s.append(" stream serialVersionUID = ").append(streamSUID);
Exception e = new InvalidClassException(s.toString());
System.out.println("Potentially Fatal Deserialization Operation. " + e);
resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
}
}
return resultClassDescriptor;
}
}
回答by Neil Coffey
I may be missing something, but it soundslike you're trying to do something more complicated than necessary. What happens if:
我可能遗漏了一些东西,但听起来您正在尝试做一些比必要更复杂的事情。如果出现以下情况会发生什么:
(a) you take the current class definition (i.e. the source code) and hard-code its serial UID to the old one (or one of the old ones), then use that class definition to deserialise the serialised instances?
(a) 您采用当前的类定义(即源代码)并将其序列 UID 硬编码为旧的(或旧的之一),然后使用该类定义反序列化序列化的实例?
(b) in the byte stream you're reading, you replace the old serial UIDs with the new one before wrapping the ObjectInputStream around them?
(b) 在您正在阅读的字节流中,在将 ObjectInputStream 包裹在它们周围之前,您将旧的串行 UID 替换为新的串行 UID?
OK, just to clarify (b). So for example, if I have a little class like this:
好的,只是为了澄清(b)。例如,如果我有一个这样的小类:
public static class MyClass implements Serializable {
static final long serialVersionUID = 0x1122334455667788L;
private int myField = 0xff;
}
then when the data is serialised, it looks something like this:
然后当数据被序列化时,它看起来像这样:
ACED000573720011746573742E546573 ?í..sr..test.Tes
74244D79436C61737311223344556677 t$MyClass."3DUfw
880200014900076D794669656C647870 ?...I..myFieldxp
000000FF ...?
Each line is 16 bytes, and each byte is 2 hex digits. If you look carefully, on the second line, 9 bytes (18 digits) in, you'll see the serial version ID starts (1122...). So in our data here (yours will differ slightly), the offset of the serial version ID is 16 + 9 = 25 (or 0x19 in hex). So before I start deserialising, if I want to change this serial version ID to something else, then I need to write my new number at offset 25:
每行是 16 个字节,每个字节是 2 个十六进制数字。如果您仔细查看,在第二行 9 个字节(18 位数字)中,您会看到序列版本 ID 以 (1122...) 开头。因此,在我们这里的数据中(您的数据会略有不同),串行版本 ID 的偏移量为 16 + 9 = 25(或 0x19 十六进制)。因此,在开始反序列化之前,如果我想将此序列版本 ID 更改为其他内容,那么我需要在偏移量 25 处写入我的新编号:
byte[] bytes = ... serialised data ...
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.putLong(25, newSerialVersionUID);
then I just proceed as normal:
然后我就照常进行:
ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes));
MyClass obj = (MyClass) oin.readObject();
回答by erickson
If you've got multiple versions of the class stored in the database, it might be pretty tricky to deserialize and upgrade them all to a consistent serialization format in a single pass.
如果您在数据库中存储了该类的多个版本,那么一次反序列化并将它们全部升级为一致的序列化格式可能会非常棘手。
If possible, you might alter the table with a column to flag whether the serialized object has been processed yet. Then make passes over the table for each serialVersionUID, where you try to process any objects that haven't been dealt with yet. You can catch the InvalidClassExceptionand go on to the next record if your updater encounters a serialized object that it doesn't handle, making a note of the version number so that you can make another pass.
如果可能,您可以使用一列更改表以标记序列化对象是否已被处理。然后为 each 遍历表serialVersionUID,在那里您尝试处理尚未处理的任何对象。InvalidClassException如果您的更新程序遇到它不处理的序列化对象,您可以捕获并继续下一条记录,记下版本号,以便您可以再次通过。
This is a little tedious, but very simple.
这有点乏味,但非常简单。
Java serialization has some very nice features to support evolution of classes. However, you have to be aware of what you are doing. It may be that all of the objects actually have the same data, but no attention was paid to maintaining the version ID.
Java 序列化有一些非常好的特性来支持类的演化。但是,您必须清楚自己在做什么。可能所有对象实际上都有相同的数据,但没有注意维护版本 ID。
You could continue to use serialization once you get all of the objects updated to the same version. Just be careful as you add new fields to the class that they make sense with their default values (booleans are false, Objects are null, ints are zero, etc).
一旦您将所有对象更新为相同版本,您就可以继续使用序列化。在向类中添加新字段时要小心,它们对其默认值有意义(布尔值为假,对象为空,整数为零等)。
回答by Tom Hawtin - tackline
You should be able to hack around the issue by overriding ObjectInputStream.readClassDescriptor.
您应该能够通过覆盖ObjectInputStream.readClassDescriptor.
Using XMLEncoderwont actually help with version migration as the compatibility rules are much the same. Really what should probably be doing is persisting the object in a relational form with the help of an ORM tool.
使用XMLEncoder实际上对版本迁移没有帮助,因为兼容性规则大同小异。真正应该做的是在 ORM 工具的帮助下以关系形式持久化对象。
Probably the different serialVersionUIDs happened due to different synthetic members being generated by javac. Head the warnings and put serialVersionUIDin.
可能serialVersionUID由于 javac 生成了不同的合成成员而导致了不同的s。头部警告并放入serialVersionUID。
回答by jim he
you can find the serial UID in HEX format, if you store serialized data in db, you can edit and replace the old UID with new serial UID in HEX format
您可以找到十六进制格式的串行 UID,如果您将序列化数据存储在 db 中,则可以使用十六进制格式的新串行 UID 编辑和替换旧 UID
回答by Pixel
Bit of a hack perhaps, but might be helpful to someone: I had a similar problem, which I solved by duplicating the offending class and settings the new class UID to 0L (for example). Then in the code which does the serialisation I copied the original object into the new object and serialised out. Then you can update your code, and deserialisation code, to use the new class in place of the old. This works perfectly, although you are stuck with the new class name. You can repeat the process, however, to recover your old class name. At the end of it, you have a fixed UID of your choosing. Tip I learned the hard way: always set your own UID !
也许有点黑客,但可能对某人有帮助:我有一个类似的问题,我通过复制有问题的类并将新类 UID 设置为 0L(例如)解决了这个问题。然后在执行序列化的代码中,我将原始对象复制到新对象中并序列化出来。然后您可以更新您的代码和反序列化代码,以使用新类代替旧类。这非常有效,尽管您坚持使用新的类名。但是,您可以重复该过程以恢复旧的类名。最后,您有一个您选择的固定 UID。提示我学会了艰难的方式:始终设置自己的 UID!

