Un nouveau
document de recherche est apparu sur le site Web OpenJDK qui décrit l'idée d'introduire une nouvelle sérialisation améliorée dans le langage pour remplacer l'ancien.
La sérialisation en Java existe depuis la version 1.1, c'est-à-dire presque depuis sa naissance. D'une part, la sérialisation est un mécanisme très pratique qui vous permet de rendre rapidement et facilement n'importe quelle classe sérialisable en héritant cette classe de l'interface java.io.Serializable. Peut-être même cette simplicité est-elle devenue l'une des principales raisons pour lesquelles Java a gagné une telle popularité dans le monde, car elle vous a permis d'écrire rapidement et efficacement des applications réseau.
D'autre part, la façon dont la sérialisation est implémentée en Java implique un grand nombre de problèmes qui augmentent le coût de prise en charge des applications, réduisent leur sécurité et ralentissent l'évolution de la plate-forme.
Quel est le problème avec la sérialisation en Java? Nous listons les problèmes les plus graves:
- La sérialisation (et la désérialisation) contourne les mécanismes du langage. Il ignore les modificateurs d'accès aux champs (privés, protégés) et crée des objets sans utiliser de constructeurs, ce qui signifie qu'il ignore les invariants qui peuvent être présents dans ces constructeurs. Un attaquant pourrait exploiter une telle vulnérabilité en substituant des données par des données invalides, et elles seraient avalées avec succès lors de la désérialisation.
- Lors de l'écriture de classes sérialisables, le compilateur n'aide en aucune façon et ne détecte pas les erreurs. Par exemple, vous ne pouvez pas garantir statiquement que tous les champs d'une classe sérialisable sont eux-mêmes sérialisables. Ou vous pouvez faire une faute de frappe dans les noms des méthodes readObject, writeObject, readResolve, etc., puis ces méthodes ne seront tout simplement pas utilisées pendant la sérialisation.
- La sérialisation ne prend pas en charge le mécanisme de versioning normal, il est donc très difficile de modifier les classes sérialisables afin qu'elles restent compatibles avec leurs anciennes versions.
- La sérialisation est fortement liée à l'encodage / décodage en streaming, ce qui signifie qu'il est très difficile de changer le format d'encodage en un format différent du format standard. De plus, le format standard n'est ni compact, ni efficace, ni lisible par l'homme.
L'erreur fondamentale de la sérialisation existante en Java est qu'elle essaie d'être trop «invisible» pour le programmeur. Il hérite simplement de java.io.Serializable et reçoit une sorte de magie implicite qui est exécutée par la machine virtuelle.
Au contraire, le programmeur doit explicitement écrire des constructions chargées de construire et de déconstruire des objets. Ces constructions doivent être au niveau du langage et doivent être écrites via un accès au champ statique, pas par réflexion.
Une autre erreur de sérialisation est qu'elle essaie d'en faire trop. Elle se donne la tâche de pouvoir sérialiser n'importe quel graphe arbitraire d'objets (qui peut contenir des boucles) et le désérialiser sans casser son état.
Cette erreur peut être corrigée en simplifiant la tâche et en sérialisant non pas un graphe d'objets, mais un arbre de données dans lequel il n'y aura pas de concept d'identité (comme dans JSON).
Comment faire une sérialisation qui s'adapte naturellement au modèle objet, utilise des constructeurs pour la désérialisation, est séparée du format d'encodage et prend en charge la gestion des versions? A cet
effet, les annotations viennent à la rescousse et la possibilité d'un langage non encore inclus en Java: le
filtrage de motifs . Par exemple:
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); } }
Dans cet exemple, la classe Range est déclarée, qui est prête pour la sérialisation via deux membres spéciaux de la classe: un sérialiseur et un désérialiseur marqués avec les annotations @Serializer et @Deserializer. Le sérialiseur est implémenté via le déconstructeur du modèle et le désérialiseur est implémenté via la méthode statique dans laquelle le constructeur est appelé. Ainsi, lors de la désérialisation, l'invariant hi> = lo spécifié dans le constructeur est inévitablement vérifié.
Il n'y a pas de magie dans cette approche, et des annotations régulières sont utilisées, donc n'importe quel framework peut faire de la sérialisation, et pas seulement la plateforme Java elle-même. Cela signifie que le format d'encodage peut également être absolument n'importe quoi (binaire, XML, JSON, YAML, etc.).
Les sérialiseurs et désérialiseurs étant des méthodes courantes, le programmeur dispose d'une grande liberté dans leur mise en œuvre. Par exemple, il peut choisir une représentation d'un objet différente de la façon dont l'objet est représenté en mémoire. Par exemple, LinkedList peut être sérialisé non pas en une chaîne de liens, mais en un tableau continu, ce qui rendra la présentation plus simple, plus efficace et plus compacte.
La gestion des versions dans cette approche est implémentée à l'aide du champ de version spécial des annotations @Serializer et @Deserializer:
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; } }
Dans cet exemple, l'un des trois désérialiseurs sera appelé, selon la version.
Que se passe-t-il si nous ne voulons pas que les sérialiseurs et désérialiseurs soient accessibles à quiconque à des fins de sérialisation? Pour ce faire, nous pouvons les rendre privés. Cependant, dans ce cas, un cadre de sérialisation spécifique ne pourra pas y accéder via la réflexion si un tel code se trouve à l'intérieur du module dans lequel le package n'est pas ouvert pour un accès par réflexion profonde. Pour un tel cas, il est proposé d'introduire une autre nouvelle construction dans le langage: les membres de la classe ouverte. Par exemple:
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; } }
Ici, les sérialiseurs et désérialiseurs sont marqués avec le mot-clé open, ce qui les rend ouverts à setAccessible.
Ainsi, la nouvelle approche est fondamentalement différente de l'ancienne: les classes y sont conçues comme sérialisables et ne sont pas données à la plate-forme telle quelle. Cela nécessite des efforts supplémentaires, mais rend la sérialisation plus prévisible, plus sûre et indépendante du format de codage et du cadre de sérialisation.
Amis PS, si vous souhaitez recevoir des informations similaires sur Java plus rapidement et plus facilement, alors abonnez-vous à
ma chaîne dans Telegram.