4.1) Versionierung:
Applikationen, in denen Code und Daten getrennt gehalten werden, haben grundsätzlich mit dem Problem der Inkonsistenz beider Bestandteile zu kämpfen. Dieses Problem tritt immer dann auf, wen Code und Daten getrennt voneinander geändert werden. Das kommt bei allen Datenbankanwendungen vor aber auch beim Serialisieren von Objekten.
JAVA versucht dieses Problem mit einem Versionierungsmechanismus zu lösen. Dieser sieht vor, dass das Interface Serializable eine long Konstante serialVersionUID enthält, in der eine Versionskennung zur Klasse gespeichert wird. Sie wird beim Aufruf von writeObject auto¬ma¬tisch berechnet und stellt einen Hashcode über die wichtigsten Eigenschaften der Klasse dar. So gehen beispielsweise Name und Signatur der Klasse, implementierte Interfaces sowie Methoden und Konstruktoren in die Berechnung ein. Selbst triviale Änderungen wie das Umbenennen oder Hinzufügen einer öffentlichen Methode verändern sie. Beim Java Paket ist das Programm Serial Version Inspector enthalten, mit welchem die Versions¬num¬mer be¬stimmt werden kann. Es wird mit dem Namen der Klasse in der Kommandozeile gestar¬tet und liefert die Versionsnummer zurück. Wird es mit dem Parameter -show aufgerufen, wird es mit einer einfachen grafischen Oberfläche gestartet.
Diese Versionsnummer wird beim Serialisieren mit in den Ausgabestream geschrieben und beim Deserialisieren wird diese Nummer mit der des aktuell kompilierten class-Files vergli¬chen. Falls diese nicht übereinstimmen wird eine InvalidClassException ausgelöst und das Deserialisieren wird abgebrochen.
Diese Art der Deserialisierung ist sehr sicher. Das bringt allerdings nicht nur Vorteile mit sich. Wenn zum Beispiel in eine Klasse eine Methode hinzugefügt wird können bereits deseriali¬sierte Objekte nicht mehr verwendet werden, obwohl das für die Wiederherstellung des Objekts nicht von Bedeutung ist.
Dieses Problem kann man aber umgehen indem man die Konstante static final long serialVersionUID definiert und mit einem Wert belegt. Dann wird die Nummer nicht mehr neu berechnet und man kann Änderungen an der Klasse vornehmen ohne, dass das Deseriali¬sieren mit einer Ausnahme abbricht. Jetzt muss natürlich der Entwickler darauf achten dass keine inkompatiblen Änderungen durchgeführt werden. Dazu gibt es einige Regeln die beach¬tet werden sollten:
. Methoden dürfen entfernt oder hinzugefügt werden
. Membervariablen dürfen entfernt werden
. Membervariablen die hinzugefügt wurden sind nach dem Deserialisieren nicht initialisiert
. Problematisch ist es meist, Membervariablen umzubenennen, sie auf transient oder static (oder umgekehrt) zu ändern, die Klasse von Serializable auf Externalizable (oder umgekehrt) zu ändern oder den Klassennamen zu ändern.
4.2) Nicht serialisierte Membervariablen:
Das sind zwei Typen von Membervariablen:
. static: Sie gehören nicht zum Objekt sondern zur Klasse des Objekts
. transient: Dieses Schlüsselwort wird verwendet, wenn es in einer Klasse Variablen gibt die nicht serialisiert werden sollen.
Beispiele für transiente Attribute:
. Werte die sich während des Programmablaufs dynamisch ergeben.
. Werte die nur für die temporäre Kommunikation zwischen zwei oder mehreren Methoden von Bedeutung sind.
. Daten die nur im Kontext der laufenden Anwendung Sinn machen: Filehandles, GUI-Ressourcen, Sockets, ...
Diese Attribute werden nach dem Deserialisieren mit dem typenspezifischen Standardwert belegt.
4.3) Objektreferenzen:
Wenn ein Objekt Membervariablen besitzt die ebenfalls Objekte sind, müssen diese Objekte ebenfalls korrekt serialisiert und deserialisiert werden. Das bedeutet dass die Objektreferenzen richtig mitgespeichert werden müssen. Besonders problematisch ist es wenn mehrere Referen¬zen auf ein Objekt vorhanden sind. Dann darf das Objekt auch nach dem Deserialisieren nur einmal vorhanden sein.
Der ObjectOutputStream enthält aus diesem Grund eine Hashtabelle, in der alle bereits serialisierten Objekte verzeichnet werden. Bei einem Aufruf von writeObject wird zunächst in dieser Tabelle nachgesehen und falls das Objekt bereits serialisiert wurde wird nur ein Verweis auf dieses Objekt gespeichert. Beim Deserialisieren eines Verweises wird dieser durch einen Verweis auf das bereits deserialisierte Objekt ersetzt. Somit werden Objekte nur einmal gespeichert, die Objektreferenzen werden gesichert und Endlosschleifen durch zyklische Verweise werden auch verhindert.
4.4) Externalizable:
Wird anstatt des Interfaces Serializable das Interface Externalizable verwendet wird nur die VersionsID in den Ausgabestream geschrieben. Für das sichern der Daten der Klasse ist diese selber verantwortlich. Es werden hierfür die beiden Methoden writeExternal und readExternal des Externalizable Interfaces implementiert. Beim Serialisieren wird dann writeExternal und beim Deserialisieren wird readExternal aufgerufen. Und der Entwickler kann selber das Format und den Inhalt der gespeicherten Daten bestimmen.
|