Java-Serialisierung - Automatische Bearbeitung geänderter Felder?

8

UPDATE 20. MAI: Ich hätte erwähnen sollen, dass bei den fraglichen Objekten "serialVersionUID" gesetzt ist (gleicher Wert in alt und neu), aber Serialisierung fehlschlägt, bevor readObject () mit folgender Ausnahme aufgerufen wird:

Exception in thread "main" java.io.InvalidClassException: TestData; incompatible types for field number

Ich füge jetzt auch ein Beispiel unten hinzu.

Ich arbeite mit einer großen Anwendung, die serialisierte Objekte (implementiert Serializable, nicht Exernalizable) von Client zu Server sendet. Leider haben wir jetzt eine Situation, in der ein existierendes Feld den Typ komplett geändert hat, was die Serialisierung unterbricht.

Der Plan besteht darin, zuerst die Serverseite zu aktualisieren. Von dort aus habe ich die Kontrolle über die neuen Versionen der serialisierten Objekte sowie über den ObjectInputStream, um die (ersten alten) Objekte zu lesen, die von den Clients kommen.

Ich hatte zuerst daran gedacht, readObject () in der neuen Version zu implementieren; Nach dem Ausprobieren stellte ich jedoch fest, dass die Validierung (aufgrund inkompatibler Typen) fehlschlug, bevor die Methode überhaupt aufgerufen wurde.

Wenn ich ObjectInputStream ableiere, kann ich erreichen, was ich will?

Noch besser, gibt es Bibliotheken von Drittanbietern, die irgendeine Art von "Magie" der Serialisierung machen? Es wäre wirklich interessant, wenn es Werkzeuge / Bibliotheken gäbe, die einen serialisierten Objektstrom in so etwas wie ein Array von HashMaps konvertieren könnten, ohne die Objekte selbst laden zu müssen. Ich bin mir nicht sicher, ob das möglich ist (konvertiere ein serialisiertes Objekt in eine HashMap, ohne die Objektdefinition selbst zu laden), aber wenn es möglich ist, könnte ich mir ein Werkzeug vorstellen, das ein serialisiertes Objekt (oder einen Strom von Objekten) konvertieren könnte ) zu einer neuen Version, die beispielsweise eine Reihe von Eigenschaften für Konvertierungshinweise / Regeln usw. verwendet.

Danke für Anregungen.

Update 20. Mai - Beispiel Quelle unten - Das Feld 'Nummer' in TestData ändert sich von einem 'int' in der alten Version zu einem 'Long' in der neuen Version. Anmerkung readObject () in der neuen Version von TestData wird nicht aufgerufen, weil eine Ausnahme ausgelöst wird, bevor sie überhaupt da ist:

Ausnahme im Thread "main" java.io.InvalidClassException: TestData; Inkompatible Typen für Feldnummer

Unten ist die Quelle. Speichern Sie es in einem Ordner und erstellen Sie dann die Unterordner "alt" und "neu". Legen Sie die "alte" Version der TestData-Klasse in den Ordner "alt" und die neue Version in "neu". Setzen Sie die Klassen "WriteIt" und "ReadIt" in den Hauptordner (Eltern von alt / neu). 'cd' in den 'alten' Ordner und kompiliere mit: javac -g -classpath . TestData.java ... dann mach dasselbe im Ordner 'new'. 'cd' zurück zum übergeordneten Ordner und kompiliere WriteIt / ReadIt:

%Vor%

Dann starte mit:

%Vor%

[ALTE Version] TestData.java

%Vor%

[NEUE Version] TestData.java

%Vor%

WriteIt.java - Alte Version von TestData zu 'TestData_old.ser' serialisieren

%Vor%

ReadIt.java - Versuch, das alte Objekt in eine neue Version von TestData zu deserialisieren. readObject () in der neuen Version wird aufgrund einer Ausnahme nicht aufgerufen.

%Vor%     
mikemky 16.05.2013, 22:41
quelle

4 Antworten

5

Ihr unmittelbares Problem scheint zu sein, dass Sie die serialVersionUID-Zeichenfolgen für Ihre Klassen nicht definiert haben. Wenn Sie dies nicht tun, generiert der Objektserialisierungs- und Deserialisierungscode die UIDs basierend auf der Repräsentationsstruktur der gesendeten und empfangenen Typen. Wenn Sie den Typ an einem Ende und nicht am anderen ändern, stimmen die UIDs nicht überein. Wenn Sie die UIDs korrigieren, wird der Lesercode über die UID-Prüfung hinauskommen, und Ihre Methode readObject wird die Möglichkeit haben, die Unterschiede in serialisierten Daten zu umgehen.

Abgesehen davon wäre mein Vorschlag:

  • Versuchen Sie, alle Clients und Server gleichzeitig zu ändern , damit Sie readObject / writeObject-Hacks nicht verwenden müssen

  • Wenn das nicht praktisch ist, versuchen Sie wegzukommen von der Verwendung von serialisierten Java-Objekten in allem, wo die Möglichkeit besteht, dass bei der Entwicklung Ihres Systems Versionsübereinstimmungen möglich sind. Verwenden Sie XML, JSON oder etwas anderes, das für "Protokoll" - oder "Schema" -Änderungen weniger empfindlich ist.

  • Wenn das nicht praktisch ist, versionieren Sie Ihr Client / Server-Protokoll.

Ich bezweifle, dass Sie durch die Unterklassenbildung der ObjectStream-Klassen etwas erreichen können. (Werfen Sie einen Blick auf den Code ... in der OpenJDK Codebasis.)

    
Stephen C 16.05.2013 22:56
quelle
2

Sie können die Serialisierung auch dann fortsetzen, wenn Änderungen an Klassen die alten serialisierten Daten mit der neuen Klasse inkompatibel machen, wenn Sie Externalizable implementieren und ein zusätzliches Feld schreiben, das die Version vor dem Schreiben der Klassendaten angibt. Dadurch kann readExternal alte Versionen auf die von Ihnen angegebene Weise behandeln. Ich kenne keine Möglichkeit, dies automatisch zu tun, aber die Verwendung dieser manuellen Methode kann für Sie funktionieren.

Hier sind die modifizierten Klassen, die kompiliert werden und ausgeführt werden, ohne eine Ausnahme auszulösen.

alt / TestData.java

%Vor%

neu / TestData.java

%Vor%

Diese Klassen können mit den folgenden Anweisungen ausgeführt werden

%Vor%

Das Ändern einer Klasse zur Implementierung von Exernalizable anstelle von Serializable führt zu folgender Ausnahme:

%Vor%

In Ihrem Fall müssten Sie die alte Version ändern, um zuerst Externalizable zu implementieren, Server und Clients gleichzeitig neu zu starten, dann könnten Sie den Server vor den Clients mit inkompatiblen Änderungen aktualisieren.

    
Chad Skeeters 27.05.2015 17:09
quelle
1
  

nachdem ich es ausprobiert habe, habe ich festgestellt, dass die Validierung fehlschlägt (wegen inkompatibler Typen), bevor die Methode jemals aufgerufen wurde

Das liegt daran, dass Sie kein serialVersionUID definiert haben. Das ist einfach zu beheben. Führen Sie einfach das Tool serialver für die alte Version der Klassen aus, und fügen Sie die Ausgabe in den neuen Quellcode ein. Sie können dann Ihre readObject() Methode auf kompatible Weise schreiben.

Außerdem könnten Sie versuchen, die Methoden readResolve() und writeReplace() zu schreiben oder serialPersistentFields auf kompatible Weise zu definieren, wenn das möglich ist. Weitere Informationen finden Sie in der Objektserialisierungsspezifikation

Siehe auch die wertvollen Vorschläge von @ StephenC.

Veröffentlichen Sie Ihre Änderung Ich schlage vor, dass Sie den Namen der Variablen und ihren Typ ändern. Das gilt als Löschen plus Einfügung, die Serialisierung-kompatibel ist, und funktioniert so lange, wie Sie nicht wollen, dass die alte int-Zahl in die neue Lange Zahl eingelesen wird (warum lang statt lang?). Wenn Sie das brauchen, schlage ich vor, die int-Zahl dort zu belassen und ein neues Long / long-Feld mit einem anderen Namen hinzuzufügen und readObject () zu ändern, um das neue Feld auf 'number' zu setzen, wenn es nicht bereits von defaultReadObject () gesetzt ist. .

    
EJP 16.05.2013 23:37
quelle
0

Wenn Sie den Typ eines Feldes ändern, sollten Sie seinen Namen ändern. Andernfalls erhalten Sie den Fehler, den Sie sehen. Wenn Sie den neuen Feldnamen aus den alten Daten setzen (oder ihn während des Lesens des Objekts anders berechnen möchten), können Sie Folgendes tun:

%Vor%

Ändern Sie nicht den Wert von serialVersionUID und das obige sollte in der Lage sein, sowohl neue als auch alte Versionen von TestData zu lesen.

    
Ted Hopp 19.07.2013 20:59
quelle

Tags und Links