Sérialisation et désérialisation XML avec Java (JAXB)
JAXB qui est l'acronyme de l'anglais « Java API for XML Binding » est une boîte à outils pour sérialisation et désérialisation d'objets java. Elle permet de voyager de manière bidirectionnelle entre les univers Java et XML grâce à un mapping (XML <-> Java). Ce mapping est flexible dans la mesure il peut être généré directement par JAXB à partir d'un schéma XML ou simplement définie par le développeur lui-même via des annotations.
Dans ce document je vais indiquer comment générer des classes java à partir d'un schéma XML puis comment aller d'un fichier XML à un objet Java et vice versa.
1. Pré-requis
- Vous êtes familier de Java 8 et de sa syntaxe.
- Vous êtes familier de XML et notamment des schémas XML.
- Vous disposez du JDK 8 minimum.
2. Vue d'ensemble
JAXB permet de réaliser 3 types d'échanges entre les univers XML et Java :
- L'association (en anglais le binding) c'est l'action qui consiste à générer des classes java à partir d'un schéma XML (extension .xsd).
- La désérialisation (en anglais l'unmarshalling) c'est l'action qui consiste à instancier des classes java à partir de données contenues dans des fichiers XML.
- La sérialisation (en anglais le marshalling) c'est l'action qui consiste à produire un fichier XML à partir d'objets java.
Pour rendre ce document digeste, je vais parcourir les API de JAXB à travers l'exemple d'un magasin de location de DVD et VHS. A chaque article loué j'associe :
- Le type d'article : DVD ou VHS
- L'identifiant du loueur.
- Le titre du film correspondant.
- La date d'emprunt.
Consultez ce fichier rentals.xml listant les locations en cours :
<?xml version="1.0" encoding="UTF-8"?> <rentals xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="rentals.xsd" xmlns="http://file.dotmyself.net/source/4/j2xml"> <rental type="DVD"> <user>1</user> <title>Casino Royal</title> <begindate>15/07/2007</begindate> </rental> <rental type="VHS"> <user>3</user> <title>Borat</title> <begindate>16/07/2007</begindate> </rental> </rentals>
3. L'association (binding)
Voici le schéma XML rentals.xsd utilisé par le magasin de location :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema targetNamespace="http://file.dotmyself.net/source/4/j2xml" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns="http://file.dotmyself.net/source/4/j2xml"> <xs:element name="begindate" type="xs:string"/> <xs:element name="rental"> <xs:complexType> <xs:sequence> <xs:element ref="user"/> <xs:element ref="title"/> <xs:element ref="begindate"/> </xs:sequence> <xs:attribute name="type" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="DVD"/> <xs:enumeration value="VHS"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> </xs:element> <xs:element name="rentals"> <xs:complexType> <xs:sequence> <xs:element ref="rental" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="title" type="xs:string"/> <xs:element name="user" type="xs:int"/> </xs:schema>
Pour générer les classes java, utilisez le compilateur xjc
comme suite :
> xjc -d src rentals.xsd parsing a schema... compiling a schema... net/dotmyself/file/source/_4/j2xml/ObjectFactory.java net/dotmyself/file/source/_4/j2xml/Rental.java net/dotmyself/file/source/_4/j2xml/Rentals.java net/dotmyself/file/source/_4/j2xml/package-info.java
Quelques explications :
- L'option -d permet d'indiquer le dossier dans lequel seront générées les classes. Ici c'est le dossier src de mon projet Java. Lisez cette page pour obtenir la liste exhaustive des options
- Le nom du package Java (e.g. net.dotmyself.file.source._4.j2xml) correspond au namespace de mon schéma XML (e.g. https://file.dotmyself.net/source/4/j2xml)
- Rental.java : est le java bean qui représente une location.
- Rentals.java : est le java bean qui représente un groupe de locations.
- ObjectFactory.java : est la fabrique d'objets java que nous utiliseront pour instancier les classes java.
- package-info.java : est la classe java qui fait le mapping entre le namespace XML (e.g. https://file.dotmyself.net/source/4/j2xml) et la package java (e.g. net.dotmyself.file.source._4.j2xml)
Vous pouvez modifier les classes produites pour les adapter plus finement à vos besoins. Néanmoins, si vous avez le choix, je vous conseille :
- De privilégier les modifications du schéma XML afin de le rendre plus/moins contraignant. A chaque mise à jour du schéma vous devrez régénérer les classes java.
- De créer vos propres fichiers de binding (extension .xjb) et de les passer en paramètre à
xjc
via l'option-b
.
4. La désérialisation (unmarshalling)
Ci-dessous la classe XML2Java.java pourvue d'une méthode main. Cette classe instancie un Unmarshaller depuis le JAXBContext puis l'utilise pour lire le fichier rentals.xml et produire des objets java :
package net.dotmyself.j2xml; import java.io.FileNotFoundException; import java.io.FileReader; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import net.dotmyself.file.source._4.j2xml.Rental; import net.dotmyself.file.source._4.j2xml.Rentals; /** * Unmarshalles the given XML Document. * @author Hyacinthe MENIET */ public class XML2Java { public static void main(String[] args) { try { if (args == null || args.length < 1) { throw new IllegalArgumentException("You must indicate a path to file to read"); } String fileName = args[0]; // Unmarshalles the given XML file to objects JAXBContext context; context = JAXBContext.newInstance("net.dotmyself.file.source._4.j2xml"); Unmarshaller unmarshaller = context.createUnmarshaller(); Rentals rentals = (Rentals) unmarshaller.unmarshal(new FileReader(fileName)); List<Rental> listOfRentals = rentals.getRental(); // Displays objects for (Rental rental : listOfRentals) { System.out.println("Type=" + rental.getType() + ", " + "User=" + rental.getUser() + ", " + "Title=" + rental.getTitle() + ", " + "Begin date=" + rental.getBegindate()); } } catch (JAXBException | FileNotFoundException ex) { Logger.getLogger(XML2Java.class.getName()).log(Level.SEVERE, null, ex); } } }
Après compilation et packaging (j2xml-1.0.jar étant le jar obtenu suite à la compilation puis au packaging des .class du projet java), le résultat de l'exécution :
> java -cp j2xml-1.0.jar net.dotmyself.j2xml.XML2Java rentals.xml Type=DVD, User=1, Title=Casino Royal, Begin date=15/07/2007 Type=VHS, User=3, Title=Borat, Begin date=16/07/2007
5. La sérialisation (marshalling)
Ci-dessous je créé la classe Java2XML.java pourvue d'une méthode main. Cette classe instancie des objets de type Rental et Rentals à partir de l'ObjectFactory et dans lesquels elle stocke des données statiques. Puis grâce au Marshaller elle produit un fichier XML :
package net.dotmyself.j2xml; import java.io.FileWriter; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import net.dotmyself.file.source._4.j2xml.ObjectFactory; import net.dotmyself.file.source._4.j2xml.Rental; import net.dotmyself.file.source._4.j2xml.Rentals; /** * Marshalles the created java Objects to the specified XML Document. * @author Hyacinthe MENIET */ public class Java2XML { public static void main(String[] args) { try { if (args == null || args.length < 1) { throw new IllegalArgumentException("You must indicate a path to file to generate"); } String fileName = args[0]; // Instanciates an ObjectFactory ObjectFactory objFactory = new ObjectFactory(); // Creates first rental Rental rental1 = objFactory.createRental(); rental1.setBegindate("15/07/2007"); rental1.setTitle("Casino Royal"); rental1.setType("DVD"); rental1.setUser(1); // Creates second rental Rental rental2 = objFactory.createRental(); rental2.setBegindate("16/07/2007"); rental2.setTitle("Borat"); rental2.setType("VHS"); rental2.setUser(3); // Stores rentals Rentals rentals = objFactory.createRentals(); rentals.getRental().add(rental1); rentals.getRental().add(rental2); // Marshalles objects to the specified file JAXBContext context = JAXBContext.newInstance(Rentals.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(rentals, new FileWriter(fileName)); } catch (JAXBException | IOException ex) { Logger.getLogger(Java2XML.class.getName()).log(Level.SEVERE, null, ex); } } }
Après compilation, l'exécution :
> java -cp j2xml-1.0.jar net.dotmyself.j2xml.Java2XML generated-rentals.xml
Ce qui génère le fichier generated-rentals.xml.
3 commentaires
Davy a dit:
Bonjour,
Merci pour cet article, j'ai compri le but et l'utilisation de l'API mais j'ai également compris de manière "claire" le but et processus de la "Sérialisation et désérialisation XML" en Java.
J'attends avec impatience de nouveau post comme celui-ci!!!!!!
Yann a dit:
Bonjour,
Merci pour cet article! Pour info en passant, le lien vers le fichier source de la classe Rentals n'est pas bon, il faudrait le remplacer par http://file.dotmyself.net/source/4/Rentals.java
Hyacinthe a dit:
Bonjour Yann,
Exact, lien corrigé.