现在,当对象具有不同的serialVersionUID时,如何反序列化数据库中持久存在的对象

我的客户端有一个oracle数据库,一个对象通过objOutStream.writeObject持久化为blob字段,该对象现在有一个不同的serialVersionUID (即使该对象没有变化,可能是不同的jvm版本),当他们尝试反序列化时抛出exception:

 java.io.InvalidClassException: CommissionResult; local class incompatible: stream classdesc serialVersionUID = 8452040881660460728, local class serialVersionUID = -5239021592691549158 

他们从一开始就没有为serialVersionUID分配一个固定的值,所以现在有些东西改变了抛出exception。 现在他们不想丢失任何数据,为此我认为最好的方法是读取对象,对它们进行反序列化,然后通过XMLEncoder再次保存它们,以避免将来的错误,如当前的“类不兼容”错误。

显然,对于该对象持有的serialVersionUID有2个不同的值,所以我想读取数据,尝试使用一个值,如果失败则尝试使用其他值,为此我尝试更改类的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(); } 

测试应该失败, java.io.InvalidClassException “本地类不兼容”,因为我在保存文件并使用新的文件读取de文件后更改了serialVersionUID ,但它没有失败,所以这意味着ObjectInputStream.readObject是没有使用我修改版的Reservation类。

有任何想法吗? 提前致谢。

!!!!!!!!!!!!!更新:

好的,有可能重新定义resultClassDescriptor来覆盖流serialVersionUID,但是,有些奇怪的事情发生了,正如我之前说的那样,似乎有2个版本的类持久存在,对象有serialVersionUID = -5239021592691549158L,其他有价值8452040881660460728L这个最后如果我没有为本地类指定值,则value是生成的值。

– 如果我没有为serialVersionUID指定值,则使用默认值(8452040881660460728L),但是无法取消具有其他值的对象的实现,会抛出一个错误,指出属性属于其他属性类型。

– 如果我指定值-5239021592691549158L,那么持久化的类将成功反序列化,但不会是其他类型,类型的相同错误。

这是错误跟踪:

可能致命的反序列化操作。 java.io.InvalidClassException:重写序列化类版本不匹配:local serialVersionUID = -5239021592691549158 stream serialVersionUID = 8452040881660460728 java.lang.ClassCastException:无法将java.util.HashMap的实例分配给字段com.posadas.ic.rules.common.commisionRules。在com.posadas.ic.rules.common.commisionRules.CommissionResult实例中的类型java.lang.String的CommissionResult.statusCode

当抛出此错误时,类的值为-5239021592691549158,如果将值更改为8452040881660460728,则该类成功反序列化,那么,会发生什么? 为什么这个错误试图为错误的类投出?

谢谢

Jorge我在http://forums.sun.com/thread.jspa?threadID=518416上找到了一个有效的解决方案。

在项目中创建以下类。 无论您何时创建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; } } 

如果您已经在数据库中存储了多个版本的类,那么在一次传递中反序列化并将它们全部升级为一致的序列化格式可能会非常棘手。

如果可能,您可以使用列更改表以标记序列化对象是否已处理。 然后为每个serialVersionUID在表上进行传递,在那里尝试处理尚未处理的任何对象。 如果您的更新程序遇到它不处理的序列化对象,您可以捕获InvalidClassException并继续下一条记录,记下版本号,以便您可以再次传递。

这有点乏味,但非常简单。

Java序列化有一些非常好的function来支持类的演变。 但是,您必须了解自己在做什么。 可能所有对象实际上都具有相同的数据,但没有注意维护版本ID。

一旦将所有对象更新到同一版本,就可以继续使用序列化。 当你向类中添加新的字段时,要小心它们的默认值是有意义的(布尔值是假的,对象是空的,整数是零,等等)。

您应该能够通过重写ObjectInputStream.readClassDescriptor来解决这个问题。

使用XMLEncoder实际上不会帮助进行版本迁移,因为兼容性规则大致相同。 真正应该做的是在ORM工具的帮助下以关系forms保持对象。

可能由于javac生成的不同合成成员而发生了不同的serialVersionUID 。 查看警告并将serialVersionUID放入。

我可能会遗漏一些东西,但听起来你正在尝试做一些比必要更复杂的事情。 如果:

(a)您获取当前的类定义(即源代码)并将其串行UID硬编码为旧的(或旧的),然后使用该类定义来反序列化序列化实例?

(b)在您正在读取的字节流中,在将ObjectInputStream包装在它们周围之前,用新的串行UID替换旧的串行UID?

好的,只是为了澄清(b)。 例如,如果我有一个像这样的小class:

  public static class MyClass implements Serializable { static final long serialVersionUID = 0x1122334455667788L; private int myField = 0xff; } 

然后当数据被序列化时,它看起来像这样:

 ACED000573720011746573742E546573 ¬í..sr..test.Tes 74244D79436C61737311223344556677 t$MyClass."3DUfw 880200014900076D794669656C647870 ?...I..myFieldxp 000000FF ...ÿ 

每行为16个字节,每个字节为2个hex数字。 如果仔细观察,在第二行,9字节(18位),您将看到序列版本ID开始(1122 …)。 因此,在我们的数据中(您的数据略有不同),串行版本ID的偏移量为16 + 9 = 25(或hex为0x19)。 所以在我开始反序列化之前,如果我想将此序列版本ID更改为其他内容,那么我需要在偏移量25处写下我的新数字:

 byte[] bytes = ... serialised data ... ByteBuffer bb = ByteBuffer.wrap(bytes); bb.putLong(25, newSerialVersionUID); 

那我就像往常一样:

 ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes)); MyClass obj = (MyClass) oin.readObject(); 

您可以找到HEX格式的串行UID,如果您在db中存储序列化数据,您可以使用HEX格式的新串行UID编辑和替换旧UID

也许是一个黑客攻击,但可能对某人有帮助:我有一个类似的问题,我通过复制违规类并将新类UID设置为0L(例如)来解决。 然后在执行序列化的代码中,我将原始对象复制到新对象中并序列化。 然后,您可以更新代码和反序列化代码,以使用新类代替旧类。 尽管您仍然坚持使用新的类名,但这种方法非常有效。 但是,您可以重复此过程以恢复旧的类名。 最后,您有一个固定的UID。 提示我学到了很多困难:始终设置自己的UID!