2009-04-27 6 views
9

Mein Client hat eine Oracle-Datenbank und ein Objekt wurde als Blob-Feld über objOutStream.writeObject beibehalten, das Objekt hat jetzt eine andere serialVersionUID (obwohl hat das Objekt keine Veränderung, vielleicht verschiedene jvm Version) und wenn sie versuchen, eine Ausnahme deserialisieren geworfen:Wie ein Objekt deserialize in einer db jetzt, wenn das Objekt hat verschiedene serialVersionUID

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

sie nicht einen festen Wert für serialVersionUID seit Anfang jetzt zugewiesen haben, so dass einige etwas geändert Diese Ausnahme wird ausgelöst. Jetzt wollen sie keine Daten verlieren, deshalb denke ich, dass das Beste darin besteht, die Objekte zu lesen, sie zu deserialisieren und sie über XMLEncoder wiederzuverwenden, um zukünftige Fehler wie den aktuellen "klasseninkompatiblen" Fehler zu vermeiden.

Offensichtlich gibt es 2 verschiedene Werte für die serialVersionUID persisted für das Objekt, also möchte ich die Daten lesen, versuchen Sie mit einem Wert und wenn es fehlschlägt dann versuchen Sie mit dem anderen Wert, Um dies zu tun habe ich versucht, die ändern serialVersionUID der Klasse mit the ASM api. Ich war in der Lage, den Wert zu ändern, aber das Problem ist, wie man die Änderung auf der Klasse aktivieren, so dass die objInpStr.readObject() die modifizierte Version der Klasse mit meiner spezifischen serializedVersionUID nehmen. Ich habe eine Testklasse die reale Umgebung zu simulieren, nehme ich ein Objekt (das als Eigenschaft hat das Objekt mit unterschiedlichem serialVersionUID Problem) das Objekt Name ist Reservation Eigentum ist 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(); 
    } 

Der Test sollte mit fehlschlagen die java.io.InvalidClassException „lokale Klasse unvereinbar“ , weil ich die serialVersionUID geändert, nachdem ich die Datei gespeichert und verwendet eine neue de Datei zu lesen, aber es funktioniert nicht nicht so bedeutet dies, dass die ObjectInputStream.readObject nicht meine modifizierte Version des Reservation mit Klasse.

Irgendwelche Ideen? Danke im Voraus.

!!!!!!!!!!!!! UPDATE:

Ok, ist es möglich, das resultClassDescriptor neu zu definieren, um den Strom serialVersionUID außer Kraft zu setzen, aber, seltsam etwas, was geschieht, wie gesagt, bevor es scheint dort sind 2 Versionen der Klasse persistent, Objekte mit serialVersionUID = -5239021592691549158L und andere mit Wert 8452040881660460728L dieser letzte Wert ist der eine generiert, wenn ich keinen Wert für die lokale Klasse angeben.

-Wenn ich verwendet keinen Wert für die serialVersionUID angeben dann wird der Standardwert (8452040881660460728L), ist aber die Objekte de-serealize nicht möglich, die den anderen Wert hat, wird ein Fehler zu sagen, dass ein geworfen Eigentum ist von einem anderen Typ.

-Wenn ich den Wert -5239021592691549158L angeben, dann werden Klassen mit diesem Wert erfolgreich de-serialisiert, aber nicht die anderen, gleichen Fehler der Typen.

dies ist der Fehlertrace:

potenziell tödliche Deserialisierung Betrieb. java.io.InvalidClassException: Überschreiben der Uneinheitlichkeit der serialisierten Klassenversion: local serialVersionUID = -5239021592691549158 stream serialVersionUID = 8452040881660460728 java.lang.ClassCastException: dem Feld com.posadas.ic.rules.common.commisionRules kann keine Instanz von java.util.HashMap zugewiesen werden .CommissionResult.statusCode vom Typ java.lang.String in der Instanz von com.posadas.ic.rules.common.commisionRules.CommissionResult

Wenn diese Fehler die Klasse hatte den Wert von -5239021592691549158 geworfen wurden, wenn eine Änderung der Wert auf 8452040881660460728 wird die Klasse erfolgreich deserialisiert, so, was passiert? Warum ist dieser Fehler, der für die falsche Klasse zu sprechen versucht?

Dank

+0

Hier bearbeiten und ersetzen ist eine ähnliche Frage http: //stackoverflow.com/questions/444909/java-modifying-serialversionuid-of-binary-serialized-object –

+0

Ja, ich habe diese Frage schon einmal gelesen, aber ich habe keine Antwort gefunden. Daniel hat gesagt, dass er einen Weg gefunden hat, es zu lösen, aber er hat es nicht getan. Ich habe genau gesagt, was ich brauche, aber ich habe keine Möglichkeit gefunden, ihn zu fragen: S –

Antwort

1

ich etwas fehlt, aber es klingt wie Sie versuchen, etwas komplizierter als nötig zu tun. Was passiert, wenn:

(a) Sie nehmen die aktuelle Klassendefinition (dh der Quellcode) und hart-Code seine serielle UID auf die alte (oder eine der alten), dann verwenden Sie diese Klassendefinition zu deserialisieren die serialisierten Instanzen?

(b) In dem Byte-Stream, den Sie gerade lesen, ersetzen Sie die alten seriellen UIDs durch die neue, bevor Sie den ObjectInputStream um sie wickeln?

OK, nur um zu klären (b). So zum Beispiel, wenn ich eine wenig Klasse wie diese:

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

dann, wenn die Daten serialisiert werden, sieht es so etwas wie diese:

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

Jede Zeile ist 16 Bytes, und jedes Byte ist 2 Hex-Ziffern. Wenn Sie genau hinschauen, sehen Sie in der zweiten Zeile, 9 Bytes (18 Ziffern), die ID der seriellen Version (1122 ...). Also in unseren Daten hier (Ihr wird leicht abweichen), der Offset der seriellen Version ID ist 16 + 9 = 25 (oder 0x19 in hex). Also, bevor ich anfangen deserialising, wenn ich diese serielle Version ID, um etwas anderes zu ändern, dann muss ich meine neue Nummer schreiben bei 25 Offset:

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

dann nur gehe ich als normal:

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

a) Das ist ein Test, den ich bereits gemacht habe, er arbeitet mit Datensätzen, bei denen das persistente Objekt den gleichen Wert hat wie der Wert, den ich festkodiere die serialVersionUID, aber th Es gibt andere Datensätze mit unterschiedlichen serialVersionUID-Werten, diese Lösung bedeutet, dass ich jeden Datensatz lesen, das Deserialisieren versuchen muss, falls es fehlschlägt, es später erneut versuchen (mit einer anderen seriellen Versions-ID neu kompilieren) und so .. bis alle Datensätze migriert sind als xml) aber es gibt mehr als 4 Millionen Datensätze: S und wenn es mehr als 2 Versionen von serialVersionID gibt .. keine Lösung, Tanks jeder Weg –

+0

b) klingt interessant, ich habe versucht, die Klasse so die Instanz zu ändern, die ObjectInputStream verwendet die serialVersionID, die ich dynamisch spezifiziere. aber .. ändern Byte-Stream durch das Finden der serialVersionUID und ändern Sie es..wie mache ich es? Es wäre nützlich, den gleichen Wert anzugeben, den das geänderte Objekt hat ... irgendein Beispiel? Danke, –

+0

Jorge, ich denke, er sagt, dass, wenn es nur zwei Werte gibt, könnten Sie die Hälfte der Datenbank mit der ersten seriellen ID verarbeiten, dann ändern Sie die Klasse, um die zweite Hälfte zu verarbeiten? –

1

Wenn mehrere Versionen der Klasse in der Datenbank gespeichert sind, kann es schwierig sein, sie zu deserialisieren und alle in einem einzigen Durchgang auf ein konsistentes Serialisierungsformat zu aktualisieren.

Wenn möglich, können Sie die Tabelle mit einer Spalte ändern, um zu kennzeichnen, ob das serialisierte Objekt bereits verarbeitet wurde. Führen Sie dann für jede serialVersionUID über die Tabelle, wo Sie versuchen, alle Objekte zu bearbeiten, die noch nicht behandelt wurden. Sie können die InvalidClassException abfangen und mit dem nächsten Datensatz fortfahren, wenn Ihr Updater auf ein serialisiertes Objekt stößt, das nicht behandelt wird. Notieren Sie sich die Versionsnummer, damit Sie einen weiteren Durchlauf durchführen können.

Dies ist ein wenig langweilig, aber sehr einfach.

Java-Serialisierung hat einige sehr nette Funktionen, um die Evolution von Klassen zu unterstützen. Sie müssen jedoch wissen, was Sie tun. Es kann sein, dass alle Objekte tatsächlich die gleichen Daten haben, aber die Pflege der Versionskennung nicht beachtet wurde.

Sie können fortfahren, Serialisierung zu verwenden, sobald Sie alle Objekte auf die gleiche Version aktualisiert haben. Seien Sie vorsichtig, wenn Sie der Klasse neue Felder hinzufügen, die mit ihren Standardwerten sinnvoll sind (Boolesche Werte sind falsch, Objekte sind null, Ints sind Null usw.).

+0

nun, das hört sich nicht schlecht an etwas, was ich versuche und vielleicht die Lösung und einfach, wenn es nur 2 Versionen gibt, aber ich würde gerne wissen, wie man den Wert der ID im laufenden Betrieb ändern, wenn ich finde, dass es nicht möglich ist, mit der Aktueller Wert, jede Art, wie ich versuche, etwas zu speichern, wie der Wert der Elemente fehlgeschlagen ist und den Prozess mit diesem Wert wiederholen, würde jede Art mit dem Spaltenflag vermeiden, bereits serialisierte Elemente zu verarbeiten, ich werde es versuchen und lassen wissen, danke erickson –

1

Sie sollten in der Lage sein, das Problem zu umgehen, indem Sie ObjectInputStream.readClassDescriptor überschreiben.

Die Verwendung von XMLEncoder wird nicht wirklich bei der Migration der Version helfen, da die Kompatibilitätsregeln sehr ähnlich sind. Eigentlich sollte das Objekt in einer relationalen Form mit Hilfe eines ORM-Tools persistiert werden.

Wahrscheinlich sind die verschiedenen serialVersionUID s passiert, weil verschiedene synthetische Mitglieder von javac erzeugt werden. Kopf die Warnungen und setzen serialVersionUID in.

+0

Die Art der Änderungen, die mein Client getan hat, ist das Hinzufügen neuer Eigenschaften zum Beispiel, in diesem Fall haben sie nicht, das spezifische Problem war mit dem Fehlen der ursprünglichen serialVersionUID, glaubst du wirklich, dass es noch mehr Probleme geben wird mit XMLEncoder wenn nur Eigenschaften hinzugefügt werden ?, Es wäre eine nette Lösung, ein ORM-Tool zu verwenden, mein mit Ruhezustand beharren das gesamte Objekt und machen ein komplettes Schema, um das zu lösen, aber ich glaube nicht, dass der Client mehr Zeit geben wird: S, ich Ich lese den ObjectInputStream-Code, um zu versuchen, sich zu deserialisieren. Danke Tom. –

+0

XMLEncoder und Java-Serialisierung verhalten sich beim Hinzufügen eines Felds ähnlich. Ein großer Vorteil der Serialisierung besteht darin, dass benutzerdefinierte Serienformulare (einschließlich des Umgangs mit serialisierten Legacy-Formularen) innerhalb des Objekts ausgeführt werden können. Sieh dir die Quelle von Java an.Bohnen, um einige hässliche Hacks zu sehen, um Standardklassen zu serialisieren. –

15

Jorge Ich fand eine Lösung auf http://forums.sun.com/thread.jspa?threadID=518416, die funktioniert.

Erstellen Sie die folgende Klasse in Ihrem Projekt. Wo immer Sie ein Objekt von ObjectInputStream erstellen, verwenden Sie stattdessen DecompressibleInputStream und es deserialisiert das alte Objekt mit der neuen Version Id-Klasse.

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; 
    } 
} 
+0

das hat mir wirklich geholfen. Danke. –

+0

vielen dank! Das hat mir geholfen, eine Menge Daten wiederherzustellen! – stantonk

+0

@Bhushan Bhangale, Hallo, als ich Ihre Antwort versuchte, java.io.StreamCorruptedException: ungültiger Typ Code: 00 kommt in meinem Code – Tenacious

1

Sie die Serien UID im HEX-Format finden können, wenn Sie in db serialisierten Daten speichern, können Sie die alte UID mit neuen Serien UID im HEX-Format