Académique Documents
Professionnel Documents
Culture Documents
JAVA II
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());
}
}
}
¨ 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
¨ 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
/* 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
/* 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
Méthode Rôle
Méthode Rôle
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;
}
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
¨ 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
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
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));
}
}
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
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
¨ 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.