Vous êtes sur la page 1sur 78

ROYAUME DU MAROC

Université Cadi Ayyad


Ecole Nationale des Sciences Appliqués
Département Génie Informatique, Réseaux & Télécoms
Safi
____________

JAVA II

2ème Année du Cycle Ingénieur :


- Option Génie Informatique (GI)
- Option Ingénieurie des Données & Intelligence Artificielle (IDIA)
m.oujaoura@uca.ma
oujaouram@yahoo.fr Mustapha OUJAOURA
Chap 03: La sérialisation
• Introduction
• La sérialisation Binaire
• La sérialisation standard
• La sérialisation personnalisée
• La sérialisation et la sécurité
• La sérialisation XML
• La sérialisation standard
• La sérialisation personnalisée
• La sérialisation JSON
Introduction
3
¨ La sérialisation d'un objet permet d'envoyer dans un flux les informations sur la classe et l'état d'un objet
pour permettre de le récréer ultérieurement. Ces informations permettent de restaurer l'état de l'objet
même dans une classe différente qui dans ce cas doit être compatible. Elle permet donc de transformer
l'état d'un objet pour permettre sa persistance en dehors de la JVM ou de l'échanger en utilisant le réseau.
¨ L'opération inverse qui consiste à créer une nouvelle instance à partir du résultat d'une sérialisation
s'appelle la désérialisation. Attention toutefois, la désérialisation de l'objet doit se faire avec la classe qui
a été utilisée pour la sérialisation ou une classe compatible.
¨ La sérialisation doit faire face à plusieurs problématiques notamment :
¤ la gestion des versions des objets
¤ la portabilité du résultat de la sérialisation
¤ les objets sérialisés font généralement référence à d'autres objets qui doivent aussi être sérialisés en même temps
pour permettre de maintenir l'état de leurs relations.
¤ la gestion des références dans un graphe d'objets sérialisés surtout si un même objet est référencé plusieurs fois dans
le graphe
¤ certains objets ne peuvent pas être sérialisés notamment ceux dépendants du système d'exploitation :
threads, fichiers, ...
¤ son utilisation peut engendrer des problèmes de sécurité : sérialisation de données sensibles, désérialisation d'un
objet dont la source est inconnue, ...
¨ Il existe plusieurs formats de sérialisation appartenant à deux grandes familles :
¤ formats binaires : c'est le format par défaut
¤ formats textes : ils sont plus portables car ils utilisent généralement une structuration standard (XML, JSON, ...),
peuvent être facilement modifiés et consomment plus de ressources pour être traités.
La sérialisation binaire
4
¨ La sérialisation binaire consiste à écrire des données présentes en mémoire vers un
flux de données binaires, ce qui donc permet de rendre les objets persistants.
¨ Java a introduit la sérialisation dans le JDK 1.1 et fournit des outils permettant de
sérialiser les objets de manière transparente et indépendante du système
d'exploitation.
La sérialisation standard
5

¨ La sérialisation en Java s'appuie sur les flux (le package


java.io).
¨ L'API Java nous fournit les outils nécessaires à la sérialisation :
¤ l'interface Serializable
¤ la classe ObjectOutputStream
¤ la classe ObjectInputStream
¨ L'interface Serializable permet d'identifier les classes
sérialisables,
¨ les classes ObjectOutputStream et ObjectInputStream
implémentent, quand à elle, les mécanismes de sérialisation et
de désérialisation afin de les abstraire.
¨ L'interface Externalizable permet d'implémenter un mécanisme
de sérialisation personalisé.
L'interface Sérializable
6

¨ Afin de pouvoir sérialiser un objet d'une classe donnée, celle-ci doit :


¤ implémenter l'interface Serializable ou
¤ hériter d'une classe sérialisable.
¨ L'interface Serializable ne possède aucun attribut et aucune
méthode, elle sert uniquement à identifier une classe sérialisable.
¨ Tous les attributs de l'objet sont sérialisés mais à certaines
conditions.
¨ Pour être sérialisé, un attribut doit :
¤ être lui-même de type sérialisable
¤ être un type primitif (qui sont tous sérialisables)
¤ ne pas être déclaré à l'aide du mot clé static
¤ ne pas être déclaré à l'aide du mot clé transient
¤ ne pas être hérité d'une classe mère sauf si celle-ci est elle-même
sérialisable
Le serialVersionUID
7

¨ Le serialVersionUID est un "numéro de version", associé à toute


classe implémentant l'interface Serializable, qui permet de s'assurer,
lors de la désérialisation, que les versions des classes Java soient
concordantes.
¨ Si le test échoue, une InvalidClassException est levée.
¨ Si une classe sérialisable ne déclare pas explicitement un
serialVersionUID, alors le mécanisme de sérialisation Java en calcule
un par défaut en se basant sur divers aspects de la classe.
¨ Il est cependant fortement recommandé de déclarer explicitement le
serialVersionUID, afin d’éviter des InvalidClassException inattendues
lors de la désérialisaton.
¨ Une classe sérialisable peut déclarer explicitement son
serialVersionUID en déclarant un attribut nommé "serialVersionUID"
qui doit être static, final et de type long , Il est également
recommandé de le déclarer aussi comme private:
private static final long serialVersionUID = 1L;
L'interface ObjectOutput
8

¨ L'interface ObjectOutput étend l'interface DataOutput pour


y ajouter l'écriture des objets.
¨ En effet, l'interface DataOutput implémente les méthodes
pour l'écriture des types primitifs, ObjectOutput
implémente en plus la possibilité d'écrire les objets et les
tableaux.
¨ Cette interface est implémentée par la classe
ObjectOutputStream qui est l’un des outils nécessaire à la
sérialisation .
La classe ObjectOutputStream
9

¨ La classe ObjectOutputStream représente "un flux objet"


qui permet de sérialiser un objet grâce à la méthode
writeObject() .
¨ Dans l’exemple suivant :
¤ On définit tout d'abord une classe Personne sérialisable.
¤ Puis on procède à la sérialisation d'un objet "pers" de cette
classe Personne, en utilisant la méthode writeObject(pers) de la
classe ObjectOutputStream pour l’enregistrer dans un fichier à
l’aide de FileOutputStream lié à ObjectOutputStream .
La classe ObjectOutputStream :
Sérialisation (Exemple)
10

import java.io.*;
class Personne implements Serializable {
static private final long serialVersionUID = 6L;
private String nom;
private Integer age;
private String password;
public Personne(String nom, Integer age, String password) {
this.nom = nom;
this.age = age;
this.password = password;
}
public String toString() {
return nom + " " + age + " ans (password : "+password + ")" ;
}
}
La classe ObjectOutputStream :
Sérialisation (Exemple)
11
public class SerializationExemple {
public static void main(String[] args) {
try {
// création d'une personne
Personne p = new Personne("Mohamed", 36, "password");
System. out.println("creation de : " + p);
// ouverture d'un flux de sortie vers le fichier "personne.serial"
FileOutputStream fos = new FileOutputStream("personne.serial");
// création d'un "flux objet" avec le flux fichier
ObjectOutputStream oos = new ObjectOutputStream(fos);
try {
// sérialisation:écriture de l'objet dans le flux de sortie
oos.writeObject(p);
oos.flush(); // on vide le tampon
System. out.println(p + " a ete serialise");
} finally {
try { // fermeture des flux
oos.close();
} finally {
fos.close();
}
}
} catch (IOException ioe) {
System. out.println(ioe.getMessage());
}
}
}

creation de : Mohamed 36 ans (password : password)


Mohamed 36 ans (password : password) a ete serialise
L'interface ObjectInput
12

¨ L'interface ObjectInput étend l'interface DataInput pour y


ajouter la lecture des objets.
¨ En effet, l'interface DataInput implémente les méthodes
pour la lecture des types primitifs, ObjectInput implémente
en plus la possibilité de lire les objets et les tableaux.
¨ Cette interface est implémentée par la classe
ObjectInputStream qui est l’un des outils nécessaire à la
sérialisation .
La classe ObjectInputStream
13

¨ Après avoir vu comment sérialiser un objet


¨ On va voir maintenant comment le désérialiser.
¨ Il s'agit évidemment de l'opération inverse, tout aussi
simple à mettre en oeuvre.
¨ Pour cela on utilise la méthode readObject() de la
classe ObjectInputStream.
La classe ObjectOutputStream :
Désérialisation (Exemple)
14
import java.io.*;
public class DeserializationExemple {
public static void main(String[] args) {
Personne p = null;
try {
// ouverture d'un flux d'entrée depuis le fichier "personne.serial"
FileInputStream fis = new FileInputStream("personne.serial");
// création d'un "flux objet" avec le flux fichier
ObjectInputStream ois = new ObjectInputStream(fis);
try {
// désérialisation:lecture de l'objet depuis le flux d'entrée
p = (Personne) ois.readObject();
} finally {
try { // on ferme les flux
ois.close();
} finally {
fis.close();
}
}
} catch (IOException ioe) {
System.out.println(ioe.getMessage());
} catch (ClassNotFoundException cnfe) {
System.out.println(cnfe.getMessage());
}
if (p != null) {
System.out.println(p + " a ete deserialise");
}
}
}

Mohamed 36 ans (password : null) a ete deserialise


Le mot clé transient
15

¨ Le mot clé transient permet d'interdire la sérialisation d'un


attribut d'une classe.
¨ Il est en général utilisé pour les données "sensibles" telles
que les mots de passe ou tout simplement pour les attributs
n'ayant pas besoin d'être sérialisé.
¨ On reprend le cas de la classe Personne de l’exemple
précédent en lui rajoutant un attribut password déclaré
avec le mot clé transient :
private transient String password;

¨ On obtient comme résultat :


Mohamed 36 ans (password : null) a ete deserialise
La sérialisation et l'héritage
16

¨ On peut distinguer deux cas de figure de comportement de la sérialisation


face à l'héritage :
¤ Premier cas de figure, très simple, lorsqu'une classe hérite d'une classe
sérialisable,
n Elle se comporte comme si elle avait implémenté elle-même l'interface Serializable.
n Les attributs de la classe mère sont sérialisés selon l'implémentation de celle-ci.
¤ Deuxième cas de figure, une classe implémente l'interface Serializable et hérite
d'une classe non sérialisable.
n Il y a ici deux points fondamentaux à savoir :
n les attributs hérités ne sont pas sérialisés.
n il est nécessaire que la classe étendue possède un constructeur par défaut accessible ; dans le
cas contraire, une InvalidClassException est levée à l'exécution.
¨ Dans l’exemple suivant, on considère une classe A non sérialisable et une
classe B héritant de A et implémentant l'interface Serializable.
¨ En exécutant, on voit bien que l'attribut string hérité de la classe A n'est pas
restauré.
¨ On Réessaye après en commentant le constructeur par défaut de la classe A,
et on voit l'InvalidClassException qui est levée.
La sérialisation et l'héritage:
Sérialisation - Désérialisation (Exemple)
17
import java.io.*;
class A {
protected String string;
// le constructeur par défaut est nécessaire
public A() {
this("<default string>");
}
public A(String string) {
this.string = string;
}
public String toString() {
return this.string;
}
}
class B extends A implements Serializable {
static private final long serialVersionUID = 7L;
private int integer;
public B(String string, int integer) {
super(string);
this.integer = integer;
}
public String toString() {
return super.toString() + " : " + this.integer;
}
}
La sérialisation et l'héritage:
Sérialisation - Désérialisation (Exemple)
18
public class SerialisationDeserialisationHeritageExemple {
public static void main(String[] args) {
try {
B b = new B("B1", 1); System.out.println(b);
FileOutputStream fos = new FileOutputStream("b.serial");
ObjectOutputStream oos = new ObjectOutputStream(fos);
try {
oos.writeObject(b);
oos.flush();
} finally {
oos.close();
fos.close();
}
b = new B("B2", 2); System.out.println(b);
FileInputStream fis = new FileInputStream("b.serial");
ObjectInputStream ois = new ObjectInputStream(fis);
try {
b = (B) ois.readObject(); System.out.println(b);
} finally {
ois.close();
fis.close();
}
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace(); B1 : 1
}
} B2 : 2
} <default string> : 1
Personnalisation de la sérialisation
19

¨ On a vu précédemment comment procéder à la


sérialisation/désérialisation d'un objet en utilisant le
mécanisme par défaut fourni par Java.
¨ Cependant, celui-ci peut se révéler limité ou inadéquat
dans certain cas.
¨ Heureusement, pour palier à cet inconvénient, Java nous
offre la possibilité de "personnaliser" la sérialisation et
même d'implémenter un mécanisme personnalisé.
Les méthodes writeObject() et
readObject()
20
¨ Il se peut qu’on aura besoin de faire un traitement particulier lors de la sérialisation
et/ou de la désérialisation comme l'écriture de données supplémentaires (par
exemple un attribut hérité d'une classe non sérialisable).
¨ Pour cela, une classe implémentant l'interface Serializable peut implémenter les
méthodes writeObject() et readObject() où on peut :
¤ définir un mécanisme personalisé de sérialisation, mais ceci uniquement pour les attributs
propres à la classe (les attributs sérialisables hérités d'une classe sérialisable reste gérés
par le mécanisme Java).
¤ sérialiser des attributs que le mécanisme par défaut ne sérialiserait pas (un attribut static
par exemple).
¨ Voici la signature des dites méthodes :
private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

¨ NB: l'écriture suivante applique le mécanisme de sérialisation par défaut :


private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // appel des mécanismes de sérialisation par défaut
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // appel des mécanismes de désérialisation par défaut
}
Personnalisation de la sérialisation :
Exemple
21

¨ Exemple :
¤ Soit une classe C implémentant Serializable et une classe D qui en hérite. La
classe D possède les attributs serialisationCount et deserialisationCount déclarés
transient , et qu’on veut incrémenter lors des opérations de
sérialisation/désérialisation.
¤ Après l’exécution du code, on voit ainsi qu’à chaque sérialisation/désérialisation le
compteur associé est incrémenté.
import java.io.*;
class C implements Serializable {
static private final long serialVersionUID = 8L;
protected String string;
public C() {
this("");
}
public C(String string) {
this.string = string;
}
public String toString() {
return this.string;
}
}
Personnalisation de la sérialisation :
Exemple
22
class D extends C {
static private final long serialVersionUID = 9L;
private int integer;
// on déclare les attributs serialisationCount et deserialisationCount transient
// afin qu'ils ne soient pas gérés par le mécanisme de sérialisation par défaut.
private transient int serialisationCount = 0;
private transient int deserialisationCount = 0;
public D(String string, int integer) {
super(string);
this.integer = integer;
}
public String toString() {
return super.toString() + " a ete serialise " + serialisationCount +
" fois et deserialise " + deserialisationCount + " fois";
}
private void writeObject(ObjectOutputStream out) throws IOException {
// appel des mécanismes de sérialisation par défaut
out.defaultWriteObject();
serialisationCount++; // on incrémente notre compteur de sérialisation
// on sérialise les attributs normalement non sérialisés
out.writeInt(serialisationCount);
out.writeInt(deserialisationCount);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// appel des mécanismes de désérialisation par défaut
in.defaultReadObject();
// on désérialise les attributs normalement non désérialisés
serialisationCount = in.readInt();
deserialisationCount = in.readInt();
deserialisationCount++; // on incrémente notre compteur de désérialisation
}
}
Personnalisation de la sérialisation :
Exemple
23
public class SerialisationDeserialisationPersonnaliseeExemple {
public static void main(String[] args) {
try {
D d = new D("D1", 1); System.out.println(d);
FileOutputStream fos = new FileOutputStream("d.serial");
ObjectOutputStream oos = new ObjectOutputStream(fos);
try {
oos.writeObject(d); System.out.println(d);
oos.flush();
} finally {
oos.close();
fos.close();
}
FileInputStream fis = new FileInputStream("d.serial");
ObjectInputStream ois = new ObjectInputStream(fis);
try {
d = (D) ois.readObject(); System.out.println(d);
} finally {
ois.close();
fis.close();
}
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
D1 a ete serialise 0 fois et deserialise 0 fois
D1 a ete serialise 1 fois et deserialise 0 fois
D1 a ete serialise 1 fois et deserialise 1 fois
Les méthodes writeReplace() et
readResolve()
24

¨ Il se peut que lors de la sérialisation et/ou la désérialisation d'un objet, on


voudrait utiliser une autre instance que celle de l'objet en question (pour
assurer l'unicité d'un singleton par exemple).
¨ Pour cela la classe sérialisable doit implémenter les méthodes writeReplace()
et readResolve() aux signatures suivantes :
Object writeReplace() throws ObjectStreamException;
Object readResolve() throws ObjectStreamException;

¨ Exemple :
¤ Prenons l'exemple d'une classe Singleton implémentant Serializable.
¤ le singleton est un patron de conception (design pattern) dont l'objectif est de
restreindre l'instanciation d'une classe à un seul objet (ou bien à quelques objets
seulement). Il est utilisé lorsqu'on a besoin exactement d'un objet pour coordonner
des opérations dans un système.
¤ A première vue il ne semble y avoir aucun problème. Cependant il y a un risque
de perdre l'unicité du Singleton.
¤ Voyons cela avec le code suivant qui ne fait que sérialiser puis désérialiser
le Singleton.
Personnalisation de la sérialisation :
Exemple
25
¨ En exécutant le code, on voit que chaque ligne affiche la référence de l'instance
du Singleton suivie de sa donnée.
¨ On voit donc que même si la donnée est bien restaurée, l'instance de notre singleton n'est plus
la même.
¨ En effet, le mécanisme de désérialisation renvoie une nouvelle instance de la classe
désérialisée, et ceci pose donc un gros problème au niveau de Singleton, qui finalement n'en
est plus un.
¨ Le mot-clé synchronized empêche toute instanciation multiple même par différents "threads".
import java.io.Serializable;
class Singleton implements Serializable {
static private final long serialVersionUID = 33L;
static private Singleton singleton = null;
private int data = 0;
private Singleton() {
} exemples.chap15.Singleton@6d06d69c : 1
public int getData() { exemples.chap15.Singleton@6d06d69c : 2
return this.data; exemples.chap15.Singleton@42a57993 : 1
}
public void setData(int data) {
this.data = data;
}
static public synchronized Singleton getSingleton() {
if ( singleton == null) singleton = new Singleton();
return singleton;
}
}
Personnalisation de la sérialisation :
Exemple
26
import java.io.*;
public class SerialisationDeserialisationPersonnaliseeExemple2 {
public static void main(String[] args) {
try {
Singleton s = Singleton.getSingleton();
s.setData(1);
System.out.println(s + " : " + s.getData());
FileOutputStream fos = new FileOutputStream("singleton.serial");
ObjectOutputStream oos = new ObjectOutputStream(fos);
try {
oos.writeObject(s); oos.flush();
} finally {
oos.close(); fos.close();
}
s.getSingleton();
s.setData(2);
System.out.println(s + " : " + s.getData());
FileInputStream fis = new FileInputStream("singleton.serial");
ObjectInputStream ois = new ObjectInputStream(fis);
try {
s = (Singleton) ois.readObject();
System.out.println(s + " : " + s.getData());
} finally {
ois.close(); fis.close();
}
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace(); exemples.chap15.Singleton@6d06d69c : 1
} exemples.chap15.Singleton@6d06d69c : 2
} exemples.chap15.Singleton@42a57993 : 1
}
Personnalisation de la sérialisation :
Exemple
27
¨ Pour résoudre ce problème, il suffit d'implémenter la méthode readResolve() dans la classe Singleton.
¨ On n'a pas besoin d'implémenter la méthode writeReplace() car le problème se situe uniquement au
niveau de la désérialisation mais son implémentation est identique.
¨ En exécutant le code, on peut voir que la troisième ligne est identique à la première et donc que l'instance
du Singleton reste bien unique .
import java.io.*;
class Singleton implements Serializable {
static private final long serialVersionUID = 33L;
static private Singleton singleton = null;
private int data = 0;
private Singleton() {
}
public int getData() {
return this.data;
} exemples.chap15.Singleton@6d06d69c : 1
public void setData(int data) { exemples.chap15.Singleton@6d06d69c : 2
this.data = data; exemples.chap15.Singleton@6d06d69c : 1
}
static public synchronized Singleton getSingleton() {
if (singleton == null) singleton = new Singleton();
return singleton;
}
protected Object readResolve() throws ObjectStreamException {
int d = getData(); // récupération des données désérialisées
Singleton s = getSingleton(); // récuperation de l'instance static de Single
s.setData(d) ;// affectation des données désérialisées à l'instance static.
return s;// on renvoie l'instance static
}
}
Personnalisation de la sérialisation :
Exemple
28
import java.io.*;
public class SerialisationDeserialisationPersonnaliseeExemple2 {
public static void main(String[] args) {
try {
Singleton s = Singleton.getSingleton();
s.setData(1);
System.out.println(s + " : " + s.getData());
FileOutputStream fos = new FileOutputStream("singleton.serial");
ObjectOutputStream oos = new ObjectOutputStream(fos);
try {
oos.writeObject(s); oos.flush();
} finally {
oos.close(); fos.close();
}
s.getSingleton();
s.setData(2);
System.out.println(s + " : " + s.getData());
FileInputStream fis = new FileInputStream("singleton.serial");
ObjectInputStream ois = new ObjectInputStream(fis);
try {
s = (Singleton) ois.readObject();
System.out.println(s + " : " + s.getData());
} finally {
ois.close(); fis.close();
}
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace(); exemples.chap15.Singleton@6d06d69c : 1
} exemples.chap15.Singleton@6d06d69c : 2
} exemples.chap15.Singleton@6d06d69c : 1
}
L'interface Externalizable
29
¨ Java abstrait à l’utilisateur le mécanisme de sérialisation afin de lui simplifier son
utilisation.
¨ On a vu qu'il était possible de personnaliser un peu ce mécanisme grâce aux
méthodes writeObject(), readObject(), writeReplace() et readResolve().
¨ Cependant, Java offre la possibilité d'implémenter son propre mécanisme de
sérialisation grâce à l'interface Externalizable (qui étend Serializable). Aux travers de
ces méthodes writeExternal() et readExternal(), cette interface permet un contrôle
total du processus de sérialisation.
¨ La première chose à savoir est qu'un objet implémentant l'interface Externalizeable
doit posséder un constructeur par défaut public.
¨ En effet, lors de la désérialisation d'un objet implémentant l'interface Externalizable,
tous les comportements de construction par défaut sont appliqués (constructeur par
défaut, initialisation lors de la déclaration des attributs).
¨ Il faut aussi savoir que si une classe implémentant Externalizable ne possède pas de
constructeur par défaut, le compilateur ne signalera aucune erreur, de même la
sérialisation de l'objet se passera sans problème, cependant, une
InvalidClassException sera levée lors de la désérialisation.
L'interface Externalizable : Exemple
30
¨ Exemple : Soit :
¤ E une classe implémentant Serializable et ayant un attribut de type String.
¤ F une classe héritant de E, implémentant l'interface Externalizable et possédant un attribut
de type int.
¤ On va personnaliser la sérialisation de la classe F en stockant le carré de l'entier et en
stockant la chaîne de caractère sous la forme d'un tableau de byte auquel on va appliqué
un cryptage de césar fonction de l'entier.
¤ L’exécution du code montre que l'interface Externalizable permet donc une liberté totale
quand à l'implémentation d’un mécanisme de sérialisation personnalisé.

import java.io.*;
class E implements Serializable {
static private final long serialVersionUID = 12L;
protected String string;
public E() {
this("");
}
public E(String string) {
this.string = string;
}
public String toString() { Chaine de caractere : 3
return this.string; null
} Chaine de caractere : 3
}
L'interface Externalizable : Exemple
31
class F extends E implements Externalizable {
static private final long serialVersionUID = 13L;
private int integer;
public F() {
this("", 0);
}
public F(String string, int integer) {
super(string);
this.integer = integer;
}
// Sérialisation des données.
public void writeExternal(ObjectOutput out) throws IOException {
// récupération du tableau de byte représentant string
byte[] b = this.string.getBytes("UTF-8");
for (int i = 0; i < b.length; i++) { // cryptage de césar avec integer
b[i] += this.integer;
}
out.writeLong(this.integer * this.integer); // écriture du carré de integer
out.writeObject(b); // écriture du tableau de byte
}
// Désérialisation des données.
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.integer = (int) Math.sqrt(in.readLong()); // lecture du carré de integer
byte[] b = (byte[]) in.readObject(); // lecture du tableau de byte
for (int i = 0; i < b.length; i++) { // decryptage de césar avec integer
b[i] -= this.integer;
}
this.string = new String(b, "UTF-8");
}
public String toString() {
return super.toString() + " : " + this.integer;
}
}
L'interface Externalizable : Exemple
32
public class InterfaceExternalizableExemple {
public static void main(String[] args) {
F f = new F("Chaine de caractere", 3);
System.out.println(f);
try {
FileOutputStream fos = new FileOutputStream("f.external");
ObjectOutputStream oos = new ObjectOutputStream(fos);
try {
oos.writeObject(f);
oos.flush();
} finally {
oos.close();
fos.close();
}
f = null;
System.out.println(f);
FileInputStream fis = new FileInputStream("f.external");
ObjectInputStream ois = new ObjectInputStream(fis);
try {
f = (F) ois.readObject();
} finally {
ois.close();
fis.close();
}
System.out.println(f);
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace(); Chaine de caractere : 3
} null
} Chaine de caractere : 3
}
La sérialisation et la sécurité
33
¨ La sérialisation n'est pas sécurisée :
¤ Même si le format par défaut est un format binaire, ce format est connu et documenté.
¤ Le contenu des données binaires peut assez facilement permettre de définir une classe qui
permettra de lire le contenu du résultat de la sérialisation.
¤ Un simple éditeur hexadécimal permet d'obtenir les valeurs des différents champs sérialisés
même ceux qui sont déclarés privés.
¨ Il ne faut pas sérialiser de données sensibles par le processus de sérialisation
standard car cela rend public ces données.
¨ Une fois un objet sérialisé, les mécanismes d'encapsulation de Java ne sont plus mis en
oeuvre : il est possible d'accéder aux champs private par exemple. Il faut soit :
¤ exclure ces champs de la sérialisation
¤ encrypter/décrypter ces données avec une personnalisation de la sérialisation
¤ encrypter tous les résultats de la sérialisation en utilisant la classe
javax.crypto.SealedObject ou la classe java.security.SignedObject qui implémente
l'interface Serializable. Elles permettent d'encrypter et de signer un objet
¤ sérialiser une instance d'un proxy qui ne contient pas ces données
¨ Le mécanisme de désérialisation permet de créer de nouvelles instances : tous les
contrôles qui sont faits dans le constructeur doivent aussi être faits lors de la
désérialisation.
La sérialisation XML
34
¨ La "sérialisation XML" est un mécanisme de persistance offret par Java depuis sa version 1.4 via les classes XMLEncoder
et XMLDecoder du package java.beans
¨ Cependant, cette appellation est un abus de langage car ce mécanisme n'est pas une vraie sérialisation même si leur
principe et leur utilisation possèdent plusieurs similitudes.
¨ La sérialisation XML possède plusieurs différences avec la sérialisation binaire:
¤ La plus évidente est que les données générées le sont dans un format textuel (XML) et non binaire.
¤ Ensuite la sérialisation XML a été spécialement conçue pour être utilisée avec les JavaBeans ce qui apporte certains avantages mais aussi
certaines limitations.
¨ La sérialisation XML présente quelques avantages :
¤ facilité de traitements et d'échanges sur le réseau grâce à l'utilisation du format XML
¤ La meilleure portabilité : il n'y a aucune dépendance avec l'implémentation propre des classes qui peuvent donc être échangées entre
différentes VMs de différents fournisseurs.
¤ Les documents XML générés sont compacts grâce à l'utilisation d'un algorithme d'élimination des redondances (le processus de
sérialisation ne tient pas compte des champs qui ont leur valeur par défaut).
¤ La tolérance aux fautes : si une erreur non structurelle est détectée, la partie affectée n'est pas prise en compte.
¤ Etant un document XML, le résultat est lisible par l'humain et exploitable par d'autres programmes.
¨ Elle a aussi plusieurs inconvénients :
¤ ne peut s'utiliser par défaut que sur des objets qui respectent la convention JavaBeans (constructeur par défaut, getter/setter pour tous
les attributs, ...)
n La classe à sérialiser doit posséder un constructeur par défaut (sans paramètre).
n Les attributs de la classe à sérialiser doivent être accessibles via des accesseurs/modifieurs.
¤ Le résultat de la sérialisation XML est plus gros que celui de la sérialisation binaire.
¨ La sérialisation XML repose principalement sur les classes XMLEncoder et XMLDecoder qui permettent de procéder à la
sérialisation et la désérialisation d'objets.
La classe XMLEncoder
35

¨ La classe XMLEncoder est la classe qui permet de sérialiser un objet vers un


document XML.
¨ La sérialisation XML ne prend en compte que les champs pour lesquels il
existe un getter et un setter public.
¨ Le mécanisme de sérialisation XML optimise le contenu du document XML en
omettant les champs dont la valeur est celle par défaut.
¨ La classe XMLEncoder possède deux constructeurs :
¤ XMLEncoder(OutputStream out) :
n Créer une nouvelle instance qui utilise le flux en paramètre pour écrire le résultat de la
sérialisation
¤ XMLEncoder(OutputStream out, String charset, boolean declaration, int
indentation) :
n Créer une nouvelle instance qui utilise le flux en paramètre pour écrire le résultat de la
sérialisation en précisant le charset, un booléen qui précise si le document XML doit
contenir la déclaration et la taille de l'indentation.
¨ Exemple : On va créer une classe XMLTools contenant une méthode statique
permettant de sérialiser un objet dans un fichier. Puis utiliser cette classe pour
sérialiser un objet.
La classe XMLEncoder : Exemple
36
/* Classe : XMLTools.java */
import java.beans.XMLEncoder;
import java.io.*;
public final class XMLTools {
private XMLTools() { }
/**Serialisation d'un objet dans un fichier
* @param object
* objet a serialiser
* @param filename
* chemin du fichier
*/
public static void encodeToFile(Object object, String fileName) throws FileNotFoundException, IOException {
// ouverture de l'encodeur vers le fichier
XMLEncoder encoder = new XMLEncoder(new FileOutputStream(fileName));
try {
encoder.writeObject(object); // serialisation de l'objet
encoder.flush();
} finally {
encoder.close(); // fermeture de l'encodeur
}
}
}

/* Classe : XMLEncoderTools.java */
public class XMLEncoderToolsExemple {
public static void main(String[] args) {
try {
User user = new User("admin", "azerty");
XMLTools.encodeToFile(user, "ressources/fichiers/user.xml");
} catch (Exception e) {
e.printStackTrace();
}
}
}
La classe XMLEncoder : Exemple
37
/* Classe : User.java */
public class User {
private String login;
private String password;
public User() {
this("anonymous", "");
}
public User(String login, String password) {
this.login = login;
this.password = password;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password){
this.password = password;
}
public String toString() {
return login;
}
}
La classe XMLEncoder
38
¨ La La structure du document XML généré utilise plusieurs conventions :
¤ le tag racine est le tag <java>. La propriété version permet de préciser la version de la
plate-forme Java utilisée pour générer le document. La propriété class permet de préciser
le nom pleinement qualifié de la classe
¤ chaque propriété est définie dans un tag dédié
¤ un tag void désigne un élément qui est passé en argument de l'élément englobant
¤ un tag object désigne un objet qui est passé en argument de l'élément englobant en
invoquant un setter
¤ les valeurs sont encapsulées dans des tags selon leur type (<int>, <string>, <long>, ...)
¤ les attributs id et idref permettent de définir et faire référence à d'autres éléments
¨ La classe XMLEncoder clone le graphe d'objets à sérialiser pour enregistrer les
opérations nécessaires et ainsi pouvoir créer le document XML
¨ Lors de la sérialisation, la classe XMLEncoder applique un algorithme qui permet
d'éviter de sérialiser les propriétés qui ont leur valeur par défaut : ceci permet de
rendre le document XML plus compact.
¨ Exemple : Reprendre l’exemple précédent en créant un objet par défaut de la classe
User.
n On remarque que les valeurs
par défaut ne sont pas sérialisées
La classe XMLDecoder
39

¨ La classe XMLDecoder est la classe qui permet de désérialiser


un objet sérialisé avec XMLEncoder à partir d’un document XML.
¨ La classe XMLDecoder possède plusieurs constructeurs :
¤ XMLDecoder(InputStream in) :
n Créer un nouveau décodeur pour désérialiser le document XML lu du flux
en paramètre
¤ XMLDecoder(InputSource is) :
n Créer un nouveau décodeur pour désérialiser le document XML lu du flux
en paramètre (depuis Java 7).
¨ Exemple : On Rajoute une méthode à la classe utilitaire
XMLTools afin de pouvoir charger un objet depuis un fichier.
Puis utiliser cette classe pour désérialiser l’objet déjà sérialisé.
La classe XMLDecoder : Exemple
40
/* Classe : XMLTools.java */
import java.beans.*;
import java.io.*;
public final class XMLTools {
private XMLTools() { }
/**Serialisation d'un objet dans un fichier
* @param object objet a serialiser
* @param filename chemin du fichier
*/
public static void encodeToFile(Object object, String fileName) throws FileNotFoundException, IOException {
// ouverture de l'encodeur vers le fichier
XMLEncoder encoder = new XMLEncoder(new FileOutputStream(fileName));
try {
encoder.writeObject(object); // serialisation de l'objet
encoder.flush();
} finally {
encoder.close(); // fermeture de l'encodeur
}
}
/** Deserialisation d'un objet depuis un fichier
* @param filename chemin du fichier
*/
public static Object decodeFromFile(String fileName) throws FileNotFoundException, IOException {
Object object = null;
XMLDecoder decoder = new XMLDecoder(new FileInputStream(fileName)); // ouverture de decodeur
try {
object = decoder.readObject(); // deserialisation de l'objet
} finally {
decoder.close(); // fermeture du decodeur
}
return object;
}
}
La classe XMLEncoder : Exemple
41
/* Classe : User.java */
public class User {
private String login;
private String password;
public User() {
this("anonymous", "");
}
public User(String login, String password) {
this.login = login;
this.password = password;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password){
this.password = password;
}
public String toString() {
return login;
}
}
La classe XMLDecoder : Exemple
42

/* Classe : XMLEncoderTools.java */
public class XMLEncoderToolsExemple {
public static void main(String[] args) {
try {
// User user = new User();
User user = new User("admin", "azerty");
XMLTools.encodeToFile(user, "ressources/fichiers/user.xml");
System.out.println(user);
user = new User("newAdmin", "123456");
System.out.println(user);
user = null;
System.out.println(user);
user = (User) XMLTools.decodeFromFile("ressources/fichiers/user.xml");
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}

admin
newAdmin
null
admin
Personnalisation de la sérialisation
43
¨ La sérialisation XML nécessite que les classes à traiter soient des JavaBeans. Ceci induit plusieurs limitations :
¤ La première est l'obligation de posséder un constructeur par défaut.
n Exemple : Essayer tout de même de sérialiser une classe n'ayant pas un constructeur par défaut. Réessayer le code précédent en commentant le
constructeur par défaut de la classe User.
n Remarquer qu'il y a une erreur lors de la sérialisation, cependant l'application continue à s'exécuter jusqu'à ce que la tentative de
désérialisation lève une exception.
n les exceptions dues à l'absence de constructeur par défaut ne sont pas propagées mais traitées directement dans la méthode writeObject() de
la classe XMLEncoder.
n Remarquer aussi malgré tout la génération d’un fichier xml qui ne contient uniquement que le prologue et le tag racine.
¤ La deuxième limitation est que chaque attribut à sérialiser doit posséder un accesseur et un modifeur (getter/setter).
n Exemple : Essayer tout de même de sérialiser une classe n'ayant pas accesseur et un modifeur (getter/setter).
n Si un attribut ne possède pas d'accesseur il ne sera pas sérialisé.
n Par contre s'il possède un accesseur mais pas de modifieur, alors une erreur (NoSuchMethodException) est générée lors de la désérialisation.
n Tout comme pour l'absence de constructeur par défaut, l'exception n'est pas propagée mais traitée directement (dans la méthode readObject()
de la classe XMLDecoder cette fois-ci).

¨ Ce système de tolérance aux erreurs assure la sérialisation des objets valides même si une erreur est détectée sur un autre
objet.
¨ La classe XMLEncoder utilise une instance de type PersistenceDelegate pour sérialiser un objet en XML. Si aucune instance
de type PersistenceDelegate n'est explicitement fournie, alors la classe XMLEncoder utilise une instance de type
DefaultPersistenceDelegate qui implémente la stratégie de sérialisation par défaut.
¨ Un des avantages de la sérialisation XML, est que dans le cas où vous implémenteriez votre propre mécanisme de
sérialisation via un PersistenceDelegate, il n'y a nul besoin d'implémenter le mécanisme de désérialisation associé.
La classe PersistenceDelegate
44

¨ La classe abstraite java.beans.PersistenceDelegate a pour


rôle d'exprimer l'état d'une instance au travers de l'utilisation
de ses méthodes publiques.
¨ Au lieu de réaliser ses actions dans la classe de l'instance à
sérialiser, comme c'est fait pour la sérialisation binaire, la
classe XMLEncoder délègue ces traitements à une instance de
type PersitanceDelegate.
¨ La classe PersistenceDelegate permet de contrôler certaines
étapes de la sérialisation :
¤ création d'une instance de l'objet par invocation d'un constructeur
¤ déterminer si une instance peut être transformée en une autre
¤ initialisation de l'instance de l'objet

¨ Elle possède plusieurs méthodes :


La classe PersistenceDelegate
45

Méthode Rôle

Initialiser la nouvelle instance


protected void initialize(Class<?> type, Object éventuellement à partir des données
oldInstance, Object newInstance, Encoder out) de la première instance fournie en
paramètre
Renvoyer une instance de type
protected abstract Expression instantiate(Object
Expression correspondant à l'objet
oldInstance, Encoder out)
fourni en paramètre
Renvoyer un booléen qui précise s'il
est possible de créer une copie de la
protected boolean mutatesTo(Object oldInstance, Object
première instance et appliquer des
newInstance)
opérations pour obtenir la seconde
instance
Réaliser les opérations déléguées
void writeObject(Object oldInstance, Encoder out) relatives à la sérialisation de
l'instance fournie en paramètre
La classe DefaultPersistenceDelegate
46

¨ La classe java.beans.DefaultPersistenceDelegate hérite de la


classe java.beans.PersistenceDelegate pour en proposer une
implémentation concrète.
¨ Cette classe est utilisée par défaut pour des classes qui doivent
respecter la convention Javabeans notamment la présence
d'un constructeur par défaut et la présence de getter/setter
pour les propriétés.
¨ Elle possède deux constructeurs, & redéfinit plusieurs méthodes:
Constructeur Rôle
DefaultPersistenceDelegate()
Créer une instance qui par défaut va invoquer le
DefaultPersistenceDelegate(String[]
constructeur avec les valeurs des propriétés de
constructorPropertyNames)
l'objet à traiter en paramètre
La classe DefaultPersistenceDelegate
47

Méthode Rôle

Initialiser les valeurs des champs de la


nouvelle instance avec celles de
protected void initialize(Class<?> type, Object
l'instance fournie en paramètres en
oldInstance, Object newInstance, Encoder out)
utilisant leurs getter/setter grâce à
l'introspection

Envoyer une instance de type


protected Expression instantiate(Object oldInstance, Expression dont le nom de méthode
Encoder out) est "new" pour préciser que c'est le
constructeur qui doit être invoqué

Si au moins un constructeur est défini


protected boolean mutatesTo(Object oldInstance, Object et la méthode equals() redéfinie alors
newInstance) renvoie la valeur de l'invocation de
oldInstance.equals(newInstance)
Empêcher la sérialisation d'un
attribut
48
¨ Il y a deux façons d'empêcher la sérialisation d'un attribut.
¤ La première est d'utiliser les limitations du mécanisme de la sérialisation XML et de ne pas
coder d'accesseur/modifieur pour l'attribut considéré.
¤ Dans le cas où l'on veuille conserver les accesseur/modifieur, il faut passer par la
classe PropertyDescriptor pour rendre l'attribut non sérialisable en mettre la valeur de la
propriété transient à true..
¨ Comme son nom l'indique, la classe PropertyDescriptor donne la description d'une
propriété d'un JavaBean. Elle fait parti de l'api d'introspection Java et s'obtient via
le BeanInfo de la classe considérée.
¨ Il faut noter que, contrairement à la sérialisation binaire, déclarer l'attribut avec le
mot clé transient n'empêche pas sa sérialisation XML, il est nécessaire de passer par
un PropertyDescriptor.
¨ Il faut utiliser l'API Introspection pour obtenir l'instance de type BeanInfo de la classe
du bean. La méthode getPropertyDescriptors() permet d'obtenir un tableau de type
PropertyDescriptor, une occurrence pour chaque champ. Il faut itérer sur ce tableau
pour trouver l'occurrence du champ concerné. Enfin, il faut invoquer sa méthode
setValue() en lui passant en paramètre « transient » et Boolean.TRUE.
¨ Exemple : Prenons la classe User de l'exemple précédent où, par soucis de sécurité,
on désire supprimer la sérialisation de l'attribut "password".
Empêcher la sérialisation d'un
attribut : Exemple
49

/* Code à rajouter pour empêcher la serialisation de l’attribut : password */


// On récupère le BeanInfo de la classe User, peut lever une IntrospectionException
BeanInfo info = Introspector.getBeanInfo(User. class);
// On récupère les PropertyDescriptors de la classe User via le BeanInfo
PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
for (PropertyDescriptor descriptor : propertyDescriptors) {
//On met la propriété "transient" à vrai pour le PropertyDescriptor de l'attribut "password"
if (descriptor.getName().equals("password")) {
descriptor.setValue("transient", Boolean. TRUE);
}
}

admin azerty
newAdmin 123456
null
admin
Sérialiser des attributs sans
accesseur/modifieur
50
¨ Les attributs ne sont sérialisés que s'ils possèdent des accesseur/modifieur.
¨ Cependant il est possible de passer outre cette limitation à condition d'avoir des
méthodes remplaçant les accesseur/modifieur.
¨ Exemple 1: empêcher la serialisation d’un attribut
¤ Prenons la classe User de l'exemple précédent où, on lui rajoute un attribut
privé address de type String.
¤ Pour accéder et modifier cet attribut on décide de créer les méthodes locate() et move() au
lieu des méthodes getAddress() et setAddress() pour empêcher la sérialisation de
l'attribut "adress".
¤ l'attribut address ne sera donc pas sauvegardé lors de la sérialisation XML.
¨ Exemple 2 : forcer la serialisation d’un attribut
¤ On continue avec l'exemple précédent.
¤ Pour forcer la sérialisation de l'attribut address, il est de nouveau nécessaire d'utiliser
l'introspection mais cette fois-ci en créant soit même le BeanInfo.
¤ Un BeanInfo est une classe donnant des informations sur la structure d'un JavaBean.
¤ Cette classe doit implémenter l'interface BeanInfo ou hériter de la classe SimpleBeanInfo.
¤ Le BeanInfo créé doit avoir le même nom que la classe qu'il décrit suivi de "BeanInfo".
¤ Ainsi pour la classe User , on doit créer une classe UserBeanInfo.
Empêcher la sérialisation d'un attribut
par accesseur/modifieur: Exemple1
51
/* Code à rajouter pour empêcher la serialisation de l’attribut : address */
private String address;
public String locate() {
return address;
}
public void move(String address) {
this.address = address;
}
public User(String login, String password, String address) {
this.login = login;
this.password = password;
this.address = address;
}

admin azerty Safi


newAdmin 123456 Rabat
null
admin
Sérialiser des attributs sans
accesseur/modifieur : Exemple2
52
public class XMLEncoderToolsExemple2 {
public static void main(String[] args) {
try {
User user = new User("admin", "azerty");
user.move("some place");
System.out.println(user.locate());
XMLTools.encodeToFile(user, "user.xml");
user.move("an other place");
System.out.println(user.locate());
user = (User) XMLTools.decodeFromFile("user.xml");
System.out.println(user.locate());
} catch (Exception e) {
e.printStackTrace();
}
}
}

some place
an other place
some place
Sérialiser une classe sans
constructeur par défaut
53
¨ Java nous offre la possibilité de modifier la façon dont le XMLEncoder va sérialiser un objet d'une classe
donnée.
¨ En effet, la classe XMLEncoder contient une table interne de PersistenceDelegates organisés en fonction de
la classe de l'objet à encoder, qui peut être interrogée ou modifiée (par la méthode
setPersistenceDelegate()) pour changer le processus d'encodage d'une classe particulière.
¨ La classe abstraite PersistenceDelegate contient une seule méthode publique writeObject() qui définit
l'implémentation de la sérialisation.
¨ Lors de la sérialisation XML, la classe XMLEncoder se contente en fait de chercher le PersistenceDelegate
associé à la classe de l'objet à sérialiser et appelle ensuite sa méthode writeObject().
¨ Si aucun PersistenceDelegate n'est trouvé, le XMLEncoder utilise alors une instance de la classe
DefaultPersistenceDelegate qui représente le mécanisme par défaut de la sérialisation XML.
¨ On va donc modifier notre classe XMLTools afin d'y ajouter une méthode encodeToFile() permettant de
sérialiser un objet en spécifiant un PersistenceDelegate.
public static void encodeToFile(Object object, String fileName, PersistenceDelegate persistenceDelegate)
throws IOException {
XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream(fileName)));
// association du PersistenceDelegate à la classe de l'objet.
encoder.setPersistenceDelegate(object.getClass(), persistenceDelegate);
try {
encoder.writeObject(object);
encoder.flush();
} finally {
encoder.close();
}
}
Sérialiser une classe sans
constructeur par défaut
54

¨ Comme on l'a vu, il n'est pas possible de sérialiser un


objet si sa classe ne possède pas de constructeur par
défaut.
¨ Grâce aux PersistenceDelegates, on va pouvoir contourner
cette limitation.
¨ Pour ce faire, plusieurs cas se présentent :
¤ 1. Les arguments du constructeur sont des attributs de la
classe
¤ 2. Les arguments du constructeur ne sont pas des attributs
de la classe.
¤ 3. Pas de constructeur mais une méthode d'instanciation
1. Les arguments du constructeur
sont des attributs de la classe
55

¨ Pour sérialiser une telle classe, il suffit d'utiliser


un DefaultPersistenceDelegate auquel ont aura précisé le
nom des attributs considérés en utilisant un tableau
de Strings.
¨ Pour illustrer ce propos, on reprend la classe User auquel
on ne laisse que le constructeur suivant:
public User(String login, String password) {
this.login = login;
this.password = password;
}
1. Les arguments du constructeur sont
des attributs de la classe: Exemple1
56
import java.beans.*;
public class XMLEncoderToolsExemple3 {
public static void main(String[] args) {
try {
// on définie les attributs de la classe utilisés par le constructeur
String[] properties = { "login", "password" };
// construction du PersistenceDelegate
PersistenceDelegate persistenceDelegate = new DefaultPersistenceDelegate(properties);
User user = new User("admin", "azerty");
XMLTools.encodeToFile(user, "ressources/fichiers/user.xml", persistenceDelegate);
System.out.println(user);
user = new User("newAdmin", "123456");
System.out.println(user);
user = (User) XMLTools.decodeFromFile("ressources/fichiers/user.xml");
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}

admin azerty null


newAdmin 123456 null
admin azerty null
2. Les arguments du constructeur ne
sont pas des attributs de la classe
57

¨ Dans le cas où les arguments du constructeur ne


correspondent pas à des attributs de la classe, il faut
utiliser un PersistenceDelegate dont on doit réécrire la
méthode instanciate().
¨ Cette méthode renvoie un objet de type Expression.
¨ Une Expression représente une méthode ou un constructeur
(dans notre cas un constructeur).
¨ Pour illustrer ceci, on modifie le constructeur de la classe
User comme suit :
//arguments du constructeur non correspondant aux attributs
public User(char[] login, char[] password) {
this.login = new String(login);
this.password = new String(password);
}
2. Les arguments du constructeur ne sont
pas des attributs de la classe: Exemple2
58
import java.beans.*;
public class XMLEncoderToolsExemple4 {
public static void main(String[] args) {
try {
// construction du PersistenceDelegate
PersistenceDelegate persistenceDelegate = new PersistenceDelegate() {
// On redéfinie la méthode instantiate
protected Expression instantiate(Object object, Encoder out) {
User user = (User) object;
// Mise en tableau des paramètres du constructeur.
// Dans notre cas, le constructeur attend deux paramètres de type char[].
// Nous récupérons donc lesattribut de type String de l’objet User
// pour en extraire les char[] associés.
Object[] params = { user.getLogin().toCharArray(), user.getPassword().toCharArray() };
// On retourne l'expression représentant le constructeur de la classe
// Le premier paramètre est l'objet.
// Le second paramètre est la classe de l'objet.
// Le troisième paramètre est la chaine de caractère "new" pour spécifier que c’est un constructeur
// Le quatrième paramètre et un tableau d'objet représentant les paramètres à passer au constructeur
return new Expression(object, object.getClass(), "new", params);
}
};
User user = new User("admin".toCharArray(), "azerty".toCharArray());
XMLTools.encodeToFile(user, "ressources/fichiers/user.xml", persistenceDelegate);
System.out.println(user);
user = new User("newAdmin".toCharArray(), "123456".toCharArray());
System.out.println(user);
user = (User) XMLTools.decodeFromFile("ressources/fichiers/user.xml");
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
admin azerty null
newAdmin 123456 null
admin azerty null
2. Les arguments du constructeur ne sont
pas des attributs de la classe: Exemple2
59
3. Pas de constructeur mais une
méthode d'instanciation
60

¨ Dans le dernier cas qui peut se présenter, la classe ne possède


pas de constructeur public et son instance ne peut être obtenue
que par l'appel à une méthode d'instanciation.
¨ La solution ressemble beaucoup à celle du cas précédent à la
différence que l'on appelle ladite méthode en lieu et place du
constructeur.
¨ On modifie la classe User en rendant son constructeur privé et
en ajoutant une méthode d'instanciation statique getUser() :
//constructeur privé
private User(String login, String password) {
this.login = login;
this.password = password;
}
public static User getUser(String login, String password) {
return new User(login, password);
}
3. Pas de constructeur mais une méthode
d'instanciation : Exemple3
61
import java.beans.*;
public class XMLEncoderToolsExemple5 {
public static void main(String[] args) {
try {
// construction du PersistenceDelegate
PersistenceDelegate persistenceDelegate = new PersistenceDelegate() {
// On redéfinie la méthode instantiate
protected Expression instantiate(Object object, Encoder out) {
User user = (User) object;
// Mise en tableau des paramètres du constructeur.
// Dans notre cas, le constructeur attend deux paramètres de type char[].
// On récupère les attribut de type String de l'objet User pour en extraire les char[] associés
Object[] params = { user.getLogin(), user.getPassword() };
// On retourne l'expression représentant le constructeur de la classe
// Le premier paramètre est l'objet.
// Le second paramètre est la classe de l'objet.
// Le troisième paramètre est le nom de la méthode.
// Le quatrième paramètre et un tableau d'objet représentant les paramètres à passer au constructeur
return new Expression(object, object.getClass(), "getUser", params);
}
};
User user = User.getUser("admin", "azerty");
XMLTools.encodeToFile(user, "ressources/fichiers/user.xml", persistenceDelegate);
System.out.println(user);
user = User.getUser("newAdmin", "123456");
System.out.println(user);
user = (User) XMLTools.decodeFromFile("ressources/fichiers/user.xml");
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
admin azerty null
newAdmin 123456 null
admin azerty null
La gestion des exceptions
62

¨ Par défaut, les classes XMLEncoder et XMLDecoder interceptent les


exceptions levées durant leurs traitements.
¨ Il peut cependant être nécessaire d'être informé de la levée d'une
exception pour permettre sa gestion.
¨ La méthode setExceptionListener() de la classe XMLEncoder permet
d'enregistrer un listener pour la gestion des exceptions.
¨ Le listener est de type ExceptionListener : il ne déclare qu'une seule
méthode exceptionThrown() qui prend en paramètre l'exception
levée.
¨ Pour associer un gestionnaire d'exceptions à une instance de type
XMLDecoder, il faut soit :
¤ fournir l'instance de type ExceptionListener en paramètre du constructeur
¤ invoquer la méthode setExceptionListener avec l'instance de type
ExceptionListener en paramètre.
La gestion des exceptions : Exemple
63
import java.beans.*;
import java.io.*;
public class XMLEncoderExceptionExemple {
public static void main(String[] args) {
Personne personne = new Personne("admin", "azerty");
System.out.println(personne);
XMLEncoder encoder = null; // Début de la serialisation
try {
encoder = new XMLEncoder(new FileOutputStream("ressources/fichiers/person.xml"));
encoder.setExceptionListener(new ExceptionListener() {
@Override
public void exceptionThrown(Exception ex) {
ex.printStackTrace();
}
});
encoder.writeObject(personne); encoder.flush();
} catch (Exception e) {
//e.printStackTrace();
} finally {
if (encoder != null) encoder.close();
}// Fin de la serialisation
personne = new Personne("newAdmin", "123456");
System.out.println(personne);
XMLDecoder decoder = null; // début de la déserialisation
try {
decoder = new XMLDecoder(new FileInputStream("ressources/fichiers/person.xml"));
decoder.setExceptionListener(new ExceptionListener() {
@Override
public void exceptionThrown(final Exception ex) {
ex.printStackTrace();
}
});
personne = (Personne) decoder.readObject();
System.out.println(personne);
} catch (Exception e) {
//e.printStackTrace();
} finally {
if (decoder != null) decoder.close();
}// Fin de la déserialisation
}
}
La sérialisation JSON
64
¨ JSON (acronyme de JavaScript Object Notation) est un format léger d'échange de
données, une alternative au XML, mais plus petit, plus rapide et plus facile à
analyser.
¨ Comme JSON utilise la syntaxe JavaScript pour décrire les objets de données, il est
indépendant du langage et de la plate-forme.
¨ La sérialisation JSON consiste à convertir un objet Java dans sa représentation JSON
(JavaScript Object Notation) et vice versa.
¨ Plusieurs bibliothèques peuvent être utiliser pour la sérialisation JSON :
¤ Google Gson Library : Le site officiel est à l'url: http://code.google.com/p/google-gson
¤ Jackson Library
¤ JSON-lib Library
¤ Flexjson Library
¤ Json-io Library
¤ Genson Library
¤ JSONiJ Library
¨ Pour utiliser Gson dans une application, il faut ajouter le fichier gson-version.jar au
classpath. (gson-2.8.5.jar)
La classe Gson
65

¨ La classe Gson est la classe principale de l'API utilisé pour lire ou écrire des
documents JSON.
¨ Une instance de la classe Gson peut être obtenue :
¤ en invoquant son constructeur pour obtenir une instance avec la configuration par
défaut qui permet de facilement répondre aux cas simples
¤ en utilisant une fabrique du type GsonBuilder qui permet de configurer
précisément l'instance créée
¨ Une instance de type Gson ne maintient aucun état lors de l'invocation de ses
méthodes.
¨ Ces deux méthodes principales possèdent plusieurs surcharges :
¤ fromJson() : permet de créer un objet qui encapsule les données d'un document
JSON. Ces surcharges attendent deux paramètres qui sont le document JSON
(JsonElement, JsonReader, Reader, String) et le type d'objet à créer (Class ou
Type)
¤ toJson() : permet de créer une représentation JSON d'un objet (Objet) ou d'un
élément (JsonElement)
La sérialisation
66

¨ La sérialisation de données dans leur représentation JSON se fait en utilisant


une des surcharges de la méthode toJson() de la classe Gson.
¨ L'objet passé en paramètre de la méthode toJson() peut être une valeur d'un
type commun (type primitif, wrapper, chaîne de caractères, tableaux).

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class JsonSerialisationExemple {
public static void main(final String[] args) {
final GsonBuilder builder = new GsonBuilder();
final Gson gson = builder.create();
System.out.println("1 -> " + gson.toJson(1));
System.out.println("abcde -> " + gson.toJson("abcde"));
System.out.println("Long(10) -> " + gson.toJson(new Long(10)));
final int[] values = { 1, 2, 3 };
System.out.println("{1,2,3} -> " + gson.toJson(values));
}
}

1 -> 1
abcde -> "abcde"
Long(10) -> 10
{1,2,3} -> [1,2,3]
La sérialisation
67

¨ Il est possible de passer un tableau en paramètre de la


méthode toJson().

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class JsonSerialisationExemple {
public static void main(final String[] args) {
final GsonBuilder builder = new GsonBuilder();
final Gson gson = builder.create();
final int[] entiers = { 1, 2, 3, 4 };
System.out.println("entiers -> " + gson.toJson(entiers));
final int[][] valeurs = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
System.out.println("valeurs -> " + gson.toJson(valeurs));
final String[] chaines = { "ab", "cd", "ef" };
System.out.println("chaines -> " + gson.toJson(chaines));
}
}

entiers -> [1,2,3,4]


valeurs -> [[1,2,3],[4,5,6],[7,8,9]]
chaines -> ["ab","cd","ef"]
La sérialisation
68
¨ Il est possible de passer une instance d'une classe qui encapsule les données à
sérialiser en paramètre de la méthode toJson().
public class Coordonnees {
private final int abscisse, ordonnee;
public Coordonnees(final int abscisse, final int ordonnee) {
this.abscisse = abscisse;
this.ordonnee = ordonnee;
}
public int getAbscisse() {
return this.abscisse;
}
public int getOrdonnee() {
return this.ordonnee;
}
@Override
public String toString() {
return "Coordonnees [abscisse=" + this.abscisse + ", ordonnee=" + this.ordonnee + "]";
}
}
import com.google.gson.*;
public class JsonSerialisationExemple3 {
public static void main(final String[] args) {
final GsonBuilder builder = new GsonBuilder();
final Gson gson = builder.create();
final Coordonnees coordonnees = new Coordonnees(120, 450);
final String json = gson.toJson(coordonnees);
System.out.println("Resultat = " + json);
}
}

Resultat = {"abscisse":120,"ordonnee":450}
La sérialisation
69
¨ Par défaut, tous les champs de la classe et des classes mères sont utilisés lors de la sérialisation
même ceux déclarés private.
¨ Tous les champs marqués avec le mot clé transient sont ignorés lors de la sérialisation. Les
champs null sont aussi par défaut ignorés durant la sérialisation.
¨ Les champs d'une classe interne qui font référence à la classe englobante sont ignorés.
¨ Il est possible de fournir une collection en paramètre de la méthode toJson().
import java.util.*;
import com.google.gson.*;
public class JsonSerialisationExemple4 {
public static void main(final String[] args) {
final GsonBuilder builder = new GsonBuilder();
final Gson gson = builder.create();
final List<Integer> listeEntier = new ArrayList<Integer>();
listeEntier.add(1);
listeEntier.add(2);
listeEntier.add(3);
System.out.println("liste -> " + gson.toJson(listeEntier));
final List listeObjet = new ArrayList();
listeObjet.add("chaine");
listeObjet.add(123);
final Coordonnees coordonnees = new Coordonnees(120, 450);
listeObjet.add(coordonnees);
final String json = gson.toJson(listeObjet);
System.out.println("Resultat = " + json);
}
}
liste -> [1,2,3]
Resultat = ["chaine",123,{"abscisse":120,"ordonnee":450}]
La sérialisation
70

¨ Il est possible de sérialiser une collection qui contient des objets de


types différents mais il ne sera pas possible de les désérialiser car
rien ne permet de préciser le type de chacune des valeurs.
¨ Les collections de type Map sont sérialisées de manière particulière.

import java.util.*;
import com.google.gson.*;
public class JsonSerialisationExemple5 {
public static void main(final String[] args) {
final GsonBuilder builder = new GsonBuilder();
final Gson gson = builder.create();
final Map<Integer, String> valeurs = new HashMap<Integer, String>();
valeurs.put(1, "Valeur1");
valeurs.put(2, "Valeur2");
valeurs.put(3, "Valeur3");
final String json = gson.toJson(valeurs);
System.out.println("Resultat = " + json);
}
}

Resultat = {"1":"Valeur1","2":"Valeur2","3":"Valeur3"}
La désérialisation
71

¨ La désérialisation de données à partir de leur représentation JSON se fait


en utilisant une des surcharges de la méthode fromJson() de la classe Gson.
¨ La méthode fromJson() possède plusieurs surcharges :
Méthode Rôle
String toJson(JsonElement jsonElement) Convertir un JsonElement dans sa représentation JSON
void toJson(JsonElement jsonElement, Ecrire la représentation JSON du JsonElement dans l'instance de
Appendable writer) type Appendable fournie en paramètre
void toJson(JsonElement jsonElement, Ecrire la représentation JSON du JsonElement dans l'instance de
JsonWriter writer) type Writer fournie en paramètre
String toJson(Object src) Convertir un objet dans sa représentation JSON
Ecrire la représentation JSON de l'objet dans l'instance de type
void toJson(Object src, Appendable writer)
Appendable fournie en paramètre
Convertir un objet typé avec un generic dans sa représentation
String toJson(Object src, Type typeOfSrc)
JSON
void toJson(Object src, Type typeOfSrc, Ecrire la représentation JSON de l'objet typé avec un generic
Appendable writer) dans l'instance de type Appendable fournie en paramètre
void toJson(Object src, Type typeOfSrc, Ecrire la représentation JSON de l'objet typé avec un generic
JsonWriter writer) dans l'instance de type JsonWriter fournie en paramètre
La désérialisation
72

¨ Exemple :
import java.util.*;
import com.google.gson.*;
public class JsonDeSerialisationExemple {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().setPrettyPrinting().create();
final int unInt = gson.fromJson("1", int.class);
System.out.println(unInt);
final Integer unInteger = gson.fromJson("1", Integer.class);
System.out.println(unInteger);
final Long unLong = gson.fromJson("1", Long.class);
System.out.println(unLong);
final Boolean booleen = gson.fromJson("false", Boolean.class);
System.out.println(booleen);
final String chaine = gson.fromJson("\"abc\"", String.class);
System.out.println(chaine);
final String[] chaine2 = gson.fromJson("[\"abc\"]", String[].class);
System.out.println(Arrays.deepToString(chaine2));
}
}

1
1
1
false
abc
[abc]
La désérialisation
73
¨ Il est possible désérialiser la représentation JSON d'un objet en la passant en
paramètre de la méthode fromJson() avec le type de la classe à produire.
public class Etudiant {
private int id;
private String nom , prenom;
public Etudiant() { }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
}
import com.google.gson.*;
public class JsonDeSerialisationExemple2 {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().create();
final String json = "{\"id\":1,\"nom\":\"nom1\",\"prenom\":\"prenom1\"}";
final Etudiant etudiant = gson.fromJson(json, Etudiant.class);
System.out.println(etudiant);
}
}
Etudiant [id=1, nom=nom1, prenom=prenom1]
La désérialisation
74
¨ Lors de la désérialisation, les valeurs qui ne sont pas contenues dans le document JSON sont
initialisées avec null.
import com.google.gson.*;
public class JsonDeSerialisationExemple3 {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().create();
final String json = "{\"id\":1,\"nom\":\"nom1\"}";
final Etudiant etudiant = gson.fromJson(json, Etudiant.class);
System.out.println(etudiant);
}
}
Etudiant [id=1, nom=nom1, prenom=null]
¨ La classe doit posséder un constructeur par défaut. Le nom de la classe n'a pas d'importance, par contre la
casse du nom des champs doit correspondre aux clés dans la représentation JSON.
¨ Gson utilise l'introspection pour alimenter les champs, donc il n'est pas obligatoire de disposer de setter
dans la classe.
import com.google.gson.*;
public class JsonDeSerialisationExemple4 {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().setPrettyPrinting().create();
final String json = "{\"ID\":1,\"NOM\":\"nom1\",\"prenom\":\"prenom1\"}";
final Etudiant etudiant = gson.fromJson(json, Etudiant.class);
System.out.println(etudiant);
}
}
Etudiant [id=1, nom=nom1, prenom=null]
La désérialisation
75
¨ Il est possible de désérialiser des données stockées dans un tableau.
import java.util.Arrays;
public class Groupe {
private final String nom;
private final Etudiant[] etudiants;
private int nbEtudiants = 0;
public Groupe(final String nom) {
this.nom = nom;
this.etudiants = new Etudiant[10];
}
public String getNom() {
return this.nom;
}
public Etudiant[] getEtudiants() {
return this.etudiants;
}
public void ajouter(final Etudiant etudiant) {
if (this.nbEtudiants < 10) this.etudiants[this.nbEtudiants++] = etudiant;
}
public String toString() {
return "Groupe [nom=" + this.nom + ", etudiants=" + Arrays.toString(this.etudiants) + ", nbEtudiants="+ this.nbEtudiants + "]";
}
}
import com.google.gson.*;
public class JsonDeSerialisationExemple5 {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().create();
final String json = "{\"nom\":\"Groupe1\",\"etudiants\":"
+ "[{\"id\":1,\"nom\":\"nom1\",\"prenom\":\"prenom1\"},"
+ "{\"id\":2,\"nom\":\"nom2\",\"prenom\":\"prenom2\"},"
+ "null],\"nbEtudiants\":2}";
final Groupe groupe = gson.fromJson(json, Groupe.class);
System.out.println(groupe);
}
}

Groupe [nom=Groupe1, etudiants=[Etudiant [id=1, nom=nom1, prenom=prenom1], Etudiant [id=2, nom=nom2, prenom=prenom2], null], nbEtudiants=2]
La désérialisation
76
¨ Il est aussi possible de désérialiser des collections. Gson permet de sérialiser une collection contenant des objets de type
divers mais ne permet pas de désérialiser une telle collection car il n'est pas possible de préciser le type de chacun des
éléments.
¨ Pour être désérialisée, une collection doit contenir des objets d'un même type précisé sous la forme d'un generic.
import java.util.*;
public class Groupe2 {
private final String nom;
private final List<Etudiant> etudiants = new ArrayList();
public Groupe2(final String nom) {
this.nom = nom;
}
public String getNom() {
return this.nom;
}
public List<Etudiant> getEtudiants() {
return this.etudiants;
}
public void ajouter(final Etudiant etudiant) {
this.etudiants.add(etudiant);
}
public String toString() {
return "Groupe [nom=" + this.nom + ", etudiants=" + this.etudiants + "]";
}
}
import com.google.gson.*;
public class JsonDeSerialisationExemple6 {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().create();
final String json = "{\"nom\":\"Groupe1\",\"etudiants\":[{\"id\":1,\"nom\":\"nom1\","
+ "\"prenom\":\"prenom1\"},{\"id\":2,\"nom\":\"nom2\",\"prenom\":\"prenom2\"}]}";
final Groupe2 groupe = gson.fromJson(json, Groupe2.class);
System.out.println(groupe);
}
}
Groupe [nom=Groupe1, etudiants=[Etudiant [id=1, nom=nom1, prenom=prenom1], Etudiant [id=2, nom=nom2, prenom=prenom2]]]
Le formatage de la représentation
JSON & Le support des valeurs null
77
¨ Par défaut, la représentation JSON faite par Gson est compacte : elle ne contient aucun élément de formatage.
¨ Il est possible de configurer l'instance de type Gson créée en utilisant un GsonBuilder pour qu'elle applique un formatage
de la représentation JSON en invoquant la méthode setPrettyPrinting().
import com.google.gson.*;
public class JsonSerialisationExemple6 {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().setPrettyPrinting().create();
final Etudiant etudiant = new Etudiant(1, "nom1", "prenom1");
final String json = gson.toJson(etudiant);
System.out.println("Resultat = " + json);
}
}
Resultat = {
"id": 1,
"nom": "nom1",
"prenom": "prenom1"
}
¨ La Par défaut, les objets null sont ignorés par Gson, ce qui permet d'avoir des représentations JSON plus compacte.
import com.google.gson.*;
public class JsonSerialisationExemple7 {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().setPrettyPrinting().create();
final Etudiant etudiant = new Etudiant(1, "nom1", null);
final String json = gson.toJson(etudiant);
System.out.println("Resultat = " + json);
}
}
Resultat = {
"id": 1,
"nom": "nom1"
}
L'exclusion de champs
78

¨ Gson propose plusieurs solutions pour exclure certains champs des opérations
de sérialisation. Si ces solutions ne sont pas suffisantes, il est toujours possible
de créer ses propres Serializer et Deserializer.
¨ Par défaut, les champs qui sont définis avec le mot clé transient ou static sont
ignorés par Gson.
¨ Il est possible de configurer l'instance de type Gson pour qu'elle ignore les
champs possédant certains modificateurs en invoquant la méthode
excludeFieldsWithModifier() de la classe GsonBuilder.
¨ L'invocation de la méthode excludeFieldsWithModifiers() permet de modifier
le comportement par défaut.
¨ Il est possible d'utiliser toutes les constantes définies dans la classe
java.lang.reflect.Modifier en paramètre de la méthode
excludeFieldsWithModifier().
¨ Exemple : Reprendre l’un des exemples précédents en rajoutant aux attributs
les modificateurs suivants : static, transient, volatile.

Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT,


Modifier.VOLATILE).create();

Vous aimerez peut-être aussi