|
|
|
|
|
|
Développons en Java v 1.60 | |
| Copyright (C) 1999-2011 Jean-Michel DOUDOUX |
![]() |
![]() |
![]() |
La sérialisation est un procédé introduit dans le JDK version 1.1 qui permet de rendre un objet persistant. Cet objet est mis sous une forme sous laquelle il pourra être reconstitué à l'identique. Ainsi il pourra être stocké sur un disque dur ou transmis au travers d'un réseau pour le créer dans une autre JVM. C'est le procédé qui est utilisé par RMI. La sérialisation est aussi utilisée par les beans pour sauvegarder leurs états.
Au travers de ce mécanisme, Java fourni une façon facile, transparente et standard de réaliser cette opération : ceci permet de facilement mettre en place un mécanisme de persistance. Il est de ce fait inutile de créer un format particulier pour sauvegarder et relire un objet. Le format utilisé est indépendant du système d'exploitation. Ainsi, un objet sérialisé sur un système peut être réutilisé par un autre système pour récréer l'objet.
L'ajout d'un attribut à l'objet est automatiquement pris en compte lors de la sérialisation. Attention toutefois, la déserialisation de l'objet doit se faire avec la classe qui a été utilisée pour la sérialisation.
La sérialisation peut s'appliquer facilement à tous les objets.
Ce chapitre contient plusieurs sections :
La sérialisation utilise l'interface Serializable et les classes ObjectOutputStream et ObjectInputStream
Cette interface ne définit aucune méthode mais permet simplement de marquer une classe comme pouvant être sérialisée.
Tout objet qui doit être sérialisé doit implémenter cette interface ou une de ses classes mères doit l'implémenter.
Si l'on tente de sérialiser un objet qui n'implémente pas l'interface Serializable, une exception NotSerializableException est levée.
| Exemple ( code Java 1.1 ) : une classe serializable possédant trois attributs |
public class Personne implements java.io.Serializable {
private String nom = "";
private String prenom = "";
private int taille = 0;
public Personne(String nom, String prenom, int taille) {
this.nom = nom;
this.taille = taille;
this.prenom = prenom;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public int getTaille() {
return taille;
}
public void setTaille(int taille) {
this.taille = taille;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
} |
Cette classe permet de sérialiser un objet.
| Exemple : sérialisation d'un objet et enregistrement sur le disque dur |
import java.io.*;
public class SerializerPersonne {
public static void main(String argv[]) {
Personne personne = new Personne("Dupond","Jean",175);
try {
FileOutputStream fichier = new FileOutputStream("personne.ser");
ObjectOutputStream oos = new ObjectOutputStream(fichier);
oos.writeObject(personne);
oos.flush();
oos.close();
}
catch (java.io.IOException e) {
e.printStackTrace();
}
}
} |
On définit un fichier avec la classe FileOutputStream. On instancie un objet de classe ObjectOutputStream en lui fournissant en paramètre le fichier : ainsi, le résultat de la sérialisation sera envoyé dans le fichier.
On appelle la méthode writeObject() en lui passant en paramètre l'objet à sérialiser. On appelle la méthode flush() pour vider le tampon dans le fichier et la méthode close() pour terminer l'opération.
Lors de ces opérations une exception de type IOException peut être levée si un problème intervient avec le fichier.
Après l'exécution de cet exemple, un fichier nommé « personne.ser » est créé. On peut visualiser son contenu mais surtout pas le modifier car sinon il serait corrompu. En effet, les données contenues dans ce fichier ne sont pas toutes au format caractères.
La classe ObjectOutputStream contient aussi plusieurs méthodes qui permettent de sérialiser des types élémentaires et non des objets : writeInt(), writeDouble(), writeFloat(), ...
Il est possible dans un même flux d'écrire plusieurs objets les uns à la suite des autres. Ainsi plusieurs objets peuvent être sauvegardés. Dans ce cas, il faut faire attention de relire les objets dans leur ordre d'écriture.
Cette classe permet de désérialiser un objet.
| Exemple ( code Java 1.1 ) : |
import java.io.*;
public class DeSerializerPersonne {
public static void main(String argv[]) {
try {
FileInputStream fichier = new FileInputStream("personne.ser");
ObjectInputStream ois = new ObjectInputStream(fichier);
Personne personne = (Personne) ois.readObject();
System.out.println("Personne : ");
System.out.println("nom : "+personne.getNom());
System.out.println("prenom : "+personne.getPrenom());
System.out.println("taille : "+personne.getTaille());
}
catch (java.io.IOException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
} |
| Résultat : |
C:\dej>java DeSerializerPersonne Personne : nom : Dupond prenom : Jean taille : 175 |
On créée un objet de la classe FileInputStream qui représente le fichier contenant l'objet sérialisé puis un objet de type ObjectInputStream en lui passant le fichier en paramètre. Un appel à la méthode readObject() retourne l'objet avec un type Object. Un cast est nécessaire pour obtenir le type de l'objet. La méthode close() permet de terminer l'opération.
Si la classe a changé entre le moment où elle a été sérialisée et le moment ou elle est désérialisée, une exception est levée :
| Exemple : la classe Personne est modifiée et recompilée |
C:\temp>java DeSerializerPersonne java.io.InvalidClassException: Personne; Local class not compatible: stream class desc serialVersionUID=-2739669178469387642 local class serialVersionUID=39870587 36962107851 atjava.io.ObjectStreamClass.validateLocalClass(ObjectStreamClass.java:4 38) at java.io.ObjectStreamClass.setClass(ObjectStreamClass.java:482) at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java :785) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:353) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:232) at java.io.ObjectInputStream.inputObject(ObjectInputStream.java:978) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:232) at DeSerializerPersonne.main(DeSerializerPersonne.java:9) |
Une exception de type StreamCorruptedException peut être levée si le fichier a été corrompu par exemple en le modifiant avec un éditeur.
| Exemple : les 2 premiers octets du fichier personne.ser ont été modifiés avec un éditeur hexa |
C:\temp>java DeSerializerPersonne java.io.StreamCorruptedException: InputStream does not contain a serialized object at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:731) at java.io.ObjectInputStream.<init>(ObjectInputStream.java:165) at DeSerializerPersonne.main(DeSerializerPersonne.java:8) |
Une exception de type ClassNotFoundException peut être levée si l'objet est transtypé vers une classe qui n'existe plus ou pas au moment de l'exécution.
| Exemple ( code Java 1.1 ) : |
C:\temp>rename Personne.class Personne2.class C:\temp>java DeSerializerPersonne java.lang.ClassNotFoundException: Personne at java.io.ObjectInputStream.inputObject(ObjectInputStream.java:981) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:232) at DeSerializerPersonne.main(DeSerializerPersonne.java:9) |
La classe ObjectInputStream possède de la même façon que la classe ObjectOutputStream des méthodes pour lire des données de type primitives : readInt(), readDouble(), readFloat(), ...
Lors de la désérialisation, le constructeur de l'objet n'est jamais utilisé.
Le contenu des attributs est visible dans le flux dans lequel est sérialisé l'objet. Il est ainsi possible pour toute personne ayant accès au flux de voir le contenu de chaque attribut même si ceux si sont private. Ceci peut poser des problèmes de sécurité surtout si les données sont sensibles.
Java introduit le mot clé transient qui précise que l'attribut qu'il qualifie ne doit pas être inclus dans un processus de sérialisation et donc de désérialisation.
| Exemple ( code Java 1.1 ) : |
... private transient String codeSecret; ... |
Lors de la désérialisation, les champs transient sont initialisés avec la valeur null. Ceci peut poser des problèmes à l'objet qui doit gérer cet état pour éviter d'avoir des exceptions de type NullPointerException.
Il est possible de personnaliser la sérialisation d'un objet. Dans ce cas, la classe doit implémenter l'interface Externalizable qui hérite de l'interface Serializable.
Cette interface définit deux méthode : readExternal() et writeExternal().
Par défaut, la sérialisation d'un objet qui implémente cette interface ne prend en compte aucun attribut de l'objet.
Remarque : le mot clé transient est donc inutile avec une classe qui implémente l'interface Externalisable
![]() |
|
La suite de cette section sera développée dans une version future de ce document
|
|
|
|
|
|
|
Développons en Java v 1.60 | ||
| Copyright (C) 1999-2011 Jean-Michel DOUDOUX |