Auf der OpenJDK-Website wurde ein neues
Forschungsdokument veröffentlicht, das die Idee beschreibt, eine neue, verbesserte Serialisierung in die Sprache einzuführen, um die alte zu ersetzen.
Die Serialisierung in Java existiert seit Version 1.1, dh fast von dem Moment an, als sie geboren wurde. Einerseits ist die Serialisierung ein sehr praktischer Mechanismus, mit dem Sie jede Klasse schnell und einfach serialisierbar machen können, indem Sie diese Klasse von der Schnittstelle java.io.Serializable erben. Vielleicht ist sogar diese Einfachheit zu einem der Hauptgründe geworden, warum Java in der Welt so große Popularität erlangt hat, weil Sie damit schnell und effizient Netzwerkanwendungen schreiben konnten.
Andererseits ist die Art und Weise, wie die Serialisierung in Java implementiert wird, mit einer Vielzahl von Problemen verbunden, die die Kosten für die Unterstützung von Anwendungen erhöhen, deren Sicherheit verringern und die Entwicklung der Plattform verlangsamen.
Was ist falsch an der Serialisierung in Java? Wir listen die schwerwiegendsten Probleme auf:
- Die Serialisierung (und Deserialisierung) umgeht Sprachmechanismen. Es ignoriert Feldzugriffsmodifikatoren (privat, geschützt) und erstellt Objekte ohne Verwendung von Konstruktoren. Dies bedeutet, dass Invarianten ignoriert werden, die in diesen Konstruktoren vorhanden sein können. Ein Angreifer könnte eine solche Sicherheitsanfälligkeit ausnutzen, indem er Daten durch ungültige Daten ersetzt, und sie würden während der Deserialisierung erfolgreich verschluckt.
- Beim Schreiben serialisierbarer Klassen hilft der Compiler in keiner Weise und erkennt keine Fehler. Beispielsweise können Sie nicht statisch garantieren, dass alle Felder einer serialisierbaren Klasse selbst serialisierbar sind. Oder Sie können einen Tippfehler in den Namen der Methoden readObject, writeObject, readResolve usw. machen. Diese Methoden werden dann während der Serialisierung einfach nicht verwendet.
- Die Serialisierung unterstützt den normalen Versionsmechanismus nicht, daher ist es sehr schwierig, serialisierbare Klassen so zu ändern, dass sie mit ihren alten Versionen kompatibel bleiben.
- Die Serialisierung ist stark an die Streaming-Codierung / -Decodierung gebunden, was bedeutet, dass es sehr schwierig ist, das Codierungsformat auf ein anderes als das Standardformat zu ändern. Darüber hinaus ist das Standardformat weder kompakt noch effizient oder für den Menschen lesbar.
Der grundlegende Fehler der vorhandenen Serialisierung in Java besteht darin, dass versucht wird, für den Programmierer zu "unsichtbar" zu sein. Es erbt einfach von java.io.Serializable und erhält eine Art implizite Magie, die von der virtuellen Maschine ausgeführt wird.
Im Gegenteil, der Programmierer muss explizit Konstruktionen schreiben, die für das Konstruieren und Dekonstruieren von Objekten verantwortlich sind. Diese Konstrukte müssen sich auf Sprachebene befinden und über statischen Feldzugriff und nicht über Reflexion geschrieben werden.
Ein weiterer Serialisierungsfehler besteht darin, dass versucht wird, zu viel zu tun. Es stellt sich die Aufgabe, einen beliebigen Graphen von Objekten (die Schleifen enthalten können) zu serialisieren und zurück zu deserialisieren, ohne seinen Zustand zu unterbrechen.
Dieser Fehler kann behoben werden, indem die Aufgabe vereinfacht wird und kein Diagramm von Objekten, sondern ein Datenbaum serialisiert wird, in dem es kein Identitätskonzept gibt (wie in JSON).
Wie kann eine Serialisierung vorgenommen werden, die auf natürliche Weise zum Objektmodell passt, Konstruktoren für die Deserialisierung verwendet, vom Codierungsformat getrennt ist und die Versionierung unterstützt? Zu diesem
Zweck helfen Anmerkungen und die Möglichkeit einer Sprache, die noch nicht in Java enthalten ist:
Pattern Matching . Zum Beispiel:
public class Range { int lo; int hi; private Range(int lo, int hi) { if (lo > hi) throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); this.lo = lo; this.hi = hi; } @Serializer public pattern Range(int lo, int hi) { lo = this.lo; hi = this.hi; } @Deserializer public static Range make(int lo, int hi) { return new Range(lo, hi); } }
In diesem Beispiel wird die Range-Klasse deklariert, die über zwei spezielle Mitglieder der Klasse zur Serialisierung bereit ist: einen Serializer und einen Deserializer, die mit den Anmerkungen @Serializer und @Deserializer gekennzeichnet sind. Der Serializer wird durch den Dekonstruktor des Musters implementiert, und der Deserializer wird durch die statische Methode implementiert, in der der Konstruktor aufgerufen wird. Während der Deserialisierung wird daher zwangsläufig die im Konstruktor angegebene Invariante hi> = lo überprüft.
Dieser Ansatz ist nicht magisch und es werden regelmäßige Anmerkungen verwendet, sodass jedes Framework Serialisierung durchführen kann und nicht nur die Java-Plattform selbst. Dies bedeutet, dass das Codierungsformat auch absolut alles sein kann (Binär, XML, JSON, YAML usw.).
Da Serializer und Deserializer übliche Methoden sind, hat der Programmierer große Freiheit bei der Implementierung. Beispielsweise kann er eine Darstellung eines Objekts auswählen, die sich von der Darstellung des Objekts im Speicher unterscheidet. Beispielsweise kann LinkedList nicht in eine Kette von Links, sondern in ein fortlaufendes Array serialisiert werden, wodurch die Präsentation einfacher, effizienter und kompakter wird.
Die Versionierung in diesem Ansatz wird mithilfe des speziellen Versionsfelds der Annotationen @Serializer und @Deserializer implementiert:
class C { int a; int b; int c; @Deserializer(version = 3) public C(int a, int b, int c) { this a = a; this.b = b; this.c = c; } @Deserializer(version = 2) public C(int a, int b) { this(a, b, 0); } @Deserializer(version = 1) public C(int a) { this(a, 0, 0); } @Serializer(version = 3) public pattern C(int a, int b, int c) { a = this.a; b = this.b; c = this.c; } }
In diesem Beispiel wird je nach Version einer der drei Deserialisierer aufgerufen.
Was ist, wenn Serialisierer und Deserialisierer nur für Serialisierungszwecke verfügbar sein sollen? Dazu können wir sie privat machen. In diesem Fall kann ein bestimmtes Serialisierungsframework jedoch nicht durch Reflektion auf sie zugreifen, wenn sich ein solcher Code in dem Modul befindet, in dem das Paket nicht für einen tiefen reflektierenden Zugriff geöffnet ist. Für einen solchen Fall wird vorgeschlagen, eine weitere neue Konstruktion in die Sprache einzuführen: offene Klassenmitglieder. Zum Beispiel:
class Foo { private final InternalState is; public Foo(ExternalState es) { this(new InternalState(es)); } @Deserializer private open Foo(InternalState is) { this.is = is; } @Serializer private open pattern serialize(InternalState is) { is = this.is; } }
Hier werden Serializer und Deserializer mit dem Schlüsselwort open gekennzeichnet, wodurch sie für setAccessible geöffnet werden können.
Daher unterscheidet sich der neue Ansatz grundlegend vom alten: Klassen werden darin als serialisierbar konzipiert und nicht so wie sie sind an die Plattform übergeben. Dies erfordert zusätzlichen Aufwand, macht die Serialisierung jedoch vorhersehbarer, sicherer und unabhängig vom Codierungsformat und Serialisierungsframework.
PS Freunde, wenn Sie ähnliche Nachrichten über Java schneller und bequemer erhalten möchten, abonnieren Sie
meinen Kanal in Telegram.