3. Annotations JAXB

3.1. Introduction

Les annotations définies par JAXB se posent sur les différents éléments d'une classe : son nom, ses champs, ses getters , ou le nom de son package. Rappelons que JAXB permet plusieurs choses :
  • générer un schéma XML à partir d'une classe Java ;
  • générer une classe Java à partir d'un schéma XML ;
  • associer des documents XML à des instances de cette classe.
Le fait est qu'un schéma XML permet de contraindre un document XML de façon plus forte qu'une classe ne contraint une instance. Par exemple, la notion d'ordre des champs dans une classe n'existe pas en Java. Au contraire, la notion d'ordre des sous-éléments d'un élément racine ou non existe en XML. Les annotations JAXB permettent donc entre autres d'exposer ces contraintes. Seule une sélection des principales annotations est présentée ici. Elles sont classées en trois catégories :
  • l'annotation qui se place sur un package ;
  • les annotations que l'on place sur les classes et énumérations ;
  • les annotations que l'on place sur les champs, ou getters.

3.2. Cas de @XmlSchema

Cette annotation permet de fixer les schémas XML utilisés pour écrire les documents XML générés, et dans le schéma XML associé au jeu de classes annotées. Elle a la particularité d'annoter un nom de package, ce qui n'est pas le cas le plus fréquemment rencontré. C'est aussi la raison pour laquelle on s'y attarde ici. On ne peut pas annoter la déclaration package dans une classe Java, ce n'est pas légal, et cela génère une erreur de compilation. Comment doit-on s'y prendre ? Pour annoter un package en Java, il faut créer un fichier nommé package-info.java, rangé à la racine de ce package. Ce fichier ressemble à un fichier de classe, sauf qu'il ne correspond pas à un nom de classe : le caractère "-" ne peut pas être utilisé pour construire un nom de classe. Ce fichier ne peut contenir que le nom du package dans lequel il se trouve. Ce nom peut porter plusieurs informations :
  • la javadoc de ce package ;
  • les annotations que l'on souhaite poser dessus.
Voyons un exemple.

Exemple 5. Un package annoté

// 
 // fichier package-info.java
 //
 @XmlSchema ( 
   xmlns = {
       @XmlNs(prefix="pau", namespaceURI="http://www.paumard.org/cours-jaxb"),
       @XmlNs(prefix="xsd", namespaceURI="http://www.w3.org/2001/XMLSchema")
   }, 
   namespace="http://www.paumard.org/cours-jaxb", 
   elementFormDefault=XmlNsForm.QUALIFIED, 
   attributeFormDefault=XmlNsForm.UNQUALIFIED
)
 package org.paumard.cours.model ;

Cette déclaration se déclinera de la façon suivante dans le schéma généré.

Exemple 6. Déclaration générée par @XmlSchema

<xs:schema  elementFormDefault="qualified"
            attributeFormDefault="unqualified"
            targetNamespace="http://www.paumard.org/cours-jaxb"
            xmlns:pau="http://www.paumard.org/cours-jaxb"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            version="1.0" >

3.3. Annotations posées sur une classe

3.3.1. Annotation @XmlRootElement

@XmlRootElement associe la classe annotée avec un nœud racine d'un document XML. Dans les cas simples, c'est la seule annotation que l'on trouve sur une classe. Cette annotation est suffisante pour générer un document XML à partir d'une instance de cette classe.

3.3.2. Annotation @XmlType

@XmlType permet plusieurs choses. Le point principal est son attribut propOrder, qui permet de fixer l'ordre dans lequel les champs de cette classe doivent être enregistrés dans le document XML.

Exemple 7. Utilisation de @XmlType

//
 // classe Marin
 //
 @XmlRootElement(name="marin")
 @XmlType(propOrder={"nom", "prenom", "age"})
 public  class Marin {
    
     @XmlAttribute(name="id")
     private  long id ;
    
     private String nom, prenom ;
     private age ;
    
     // reste de la classe
}

3.3.3. Annotation @XmlAccessorType

@XmlAccessorType indique comment JAXB doit prendre en compte les champs d'une classe. Tous les champs ou propriétés d'une classe sont pris en compte par défaut dans le processus de génération de documents XML, sauf ceux qui sont annotés @XmlTransient. Il est important de noter que, par défaut, JAXB prend en compte toutes les propriétés publiques, et les champs annotés. Si l'on annote un champ privé, sans annoter la classe avec @XmlAccessType.FIELD, les choses ont toutes les chances de mal se passer. Effectivement JAXB verra deux fois le même champ : une fois du fait de l'annotation, et la deuxième fois du fait qu'il prend en compte le getter public. Les valeurs que peut prendre cette annotation sont les suivantes :
  • @XmlAccessType.FIELD : indique que tous les champs non statiques de la classe sont pris en compte ;
  • @XmlAccessType.PROPERTY : indique que toutes les paires de getters / setters sont prises en compte ;
  • @XmlAccessType.PUBLIC : indique que toutes les paires de getters / setters et tous les champs publics non statiques seront pris en compte ;
  • @XmlAccessType.NONE : indique qu'aucun champ ou propriété n'est pris en compte.

3.3.4. Annotations @XmlEnum et @XmlEnumValue

@XmlEnum permet de préciser la façon dont les valeurs d'une énumération vont être écrites dans le code XML. @XmlEnum fonctionne comme l'annotation JPA @EnumeratedType, qui permet d'imposer d'écrire en base le nom des valeurs énumérées plutôt que le numéro d'ordre. Voyons un exemple de fonctionnement.

Exemple 8. Première utilisation de @XmlEnum et @XmlEnumValue

//
 // énumération Grade
 //
 @XmlType
 @XmlEnum(String.class)
 public enum Grade {
    
    MATELOT, BOSCO, PACHA, CUISINIER
}

 //
 // Schéma XML généré
 //
<xsd:simpleType name="Grade">
    <xsd:restriction base="xsd:string"/>
    <xsd:enumeration value="MATELOT"/>
    <xsd:enumeration value="BOSCO"/>
    <xsd:enumeration value="PACHA"/>
    <xsd:enumeration value="CUISINIER"/>
</xsd:simpleType>

Dans ce premier exemple, les valeurs enumérées seront écrites par leur nom. Il s'agit en fait du comportement par défaut de JAXB, on aurait donc pu faire l'économie de cette déclaration. Notons que la restriction xsd est imposée par la classe portée en attribut de @XmlEnum.

Exemple 9. Deuxième utilisation de @XmlEnum et @XmlEnumValue

//
 // énumération Grade
 //
 @XmlType
 @XmlEnum(Integer.class)
 public enum Grade {
    
     @XmlEnumValue("10") MATELOT, 
     @XmlEnumValue("20") BOSCO, 
     @XmlEnumValue("30") PACHA, 
     @XmlEnumValue("40") CUISINIER
}

 //
 // Schéma XML généré
 //
<xsd:simpleType name="Grade">
    <xsd:restriction base="xsd:int"/>
    <xsd:enumeration value="10"/>
    <xsd:enumeration value="20"/>
    <xsd:enumeration value="30"/>
    <xsd:enumeration value="40"/>
</xsd:simpleType>

Dans ce deuxième exemple, le type de base utilisé est int. Il nous faut donc préciser les valeurs des entiers associées aux valeurs de notre énumération. C'est le rôle de l'annotation @XmlEnumValue, qui se place sur chaque valeur de l'énumération, et qui porte en attribut l'entier associé à cette valeur.

3.4. Annotations posées sur un champ ou un getter

3.4.1. Annotation @XmlElement

L'annotation @XmlElement permet d'associer un champ ou un getter à un nœud d'un document XML. Il permet de spécifier le nom de cet élément, son espace de nom, sa valeur par défaut, etc...

3.4.2. Annotation @XmlTransient

Cette annotation posée sur un champ ou un getter le retire des éléments pris en compte pour la création des schémas et des documents XML.

3.4.3. Annotation @XmlElementWrapper et @XmlElements

Si le champ ou le getter que l'on annote est de type List, alors on peut spécifier les choses plus finement. Une List Java permet d'enregistrer des éléments de différents types, soit non spécifiés (comme en Java 4), soit étendant une classe ou une interface donnée dans la déclaration de cette liste. Dans les deux cas, une liste peut porter des éléments de différentes classes. En utilisant l'annotation @XmlElements, on peut spécifier que chaque élément de la liste, du fait de sa classe, est associée à un nœud différent. Examinons l'exemple suivant, dans lequel les classes Cuisinier et Capitaine étendent Marin.

Exemple 10. Utilisation de @XmlElements

// 
 // classe Bateau
 //
 @XmlRootElement(name="bateau")
 @XmlAccessorType(XmlAccessType.FIELD)
 public  class Bateau {

     @XmlAttribute
     private  long id ;

     @XmlElement(name="nom")
     private String nom ;

     @XmlElementWrapper(name="equipage")
     @XmlElements({
        @XmlElement(name="marin",     type=Marin.class),
        @XmlElement(name="capitaine", type=Capitaine.class),
        @XmlElement(name="cuisinier", type=Cuisinier.class)
    })
     private List<Marin> equipage =  new ArrayList<Marin>() ;

     // reste de la classe
}

Voyons un exemple de document XML généré à partir de cette classe annotée.

Exemple 11. Utilisation de @XmlElements : document XML généré

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <bateau  xmlns:ns2="http://www.paumard.org/cours-jaxb"  id="3">
     <nom>Altaïr</nom>
     <equipage>
         <marin  id="10">
             <nom>Surcouf</nom>
             <age>0</age>
         </marin>
         <cuisinier  id="20">
             <nom>Cook</nom>
             <age>0</age>
         </cuisinier>
         <capitaine  id="30">
             <nom>Magellan</nom>
             <age>0</age>
         </capitaine>
     </equipage>
 </bateau>

Ce document XML porte bien les éléments de notre liste tels que nous les avons spécifiés : les instances de Capitaine dans un élément capitaine etc... Notons la présence de l'annotation @XmlWrapper sur le champ equipage. C'est cette annotation qui permet de ranger les éléments marin, capitaine et cuisinier dans le sous-élément equipage. Si elle n'avait pas été présente, ces éléments auraient été rangés à plat dans l'élément bateau.

3.4.4. Annotation @XmlList

L'annotation @XmlList se pose sur des champs ou getters de type List, et n'est valide que lorsque ces listes portent des types primitifs Java, des classes enveloppe, ou des chaînes de caractères ( String). Dans ce cas, la liste est écrite dans un unique élément XML, et ses éléments sont séparés par des espaces. Voyons ceci sur un exemple.

Exemple 12. Utilisation de @XmlList

//
 // Classe Tableau
 //
 @XmlRootElement
 public  class Tableau {

     @XmlElement
     @XmlList
     private List<String> nombres =  new ArrayList<String>() ;

     public List<String> getNombres() {
         return  this.nombres ;
    }

	 // reste de la classe
}

 //
 // code d'utilisation
 //
 public  static  void main(String... args)  throws JAXBException {

    Tableau tableau =  new Tableau() ;
    tableau.getNombres().add("un") ;
    tableau.getNombres().add("deux") ;
    tableau.getNombres().add("trois") ;

    JAXBContext context = JAXBContext.newInstance(Tableau.class) ;

    Marshaller marshaller = context.createMarshaller() ;
    marshaller.setProperty("jaxb.encoding",  "UTF-8") ;
    marshaller.setProperty("jaxb.formatted.output", true) ;
    marshaller.marshal(tableau,  new File("tableau.xml")) ;
}

L'exécution de ce code génère le fichier suivant. On voit que les éléments de la liste nombre sont écrits dans le même éléments XML nombres.

Exemple 13. Utilisation de @XmlList : document XML généré

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

 <tableau>
     <nombres>un deux trois</nombres>
 </tableau>

3.4.5. Annotation @XmlAttribute

Cet élément permet d'écrire le champ annoté dans un attribut XML plutôt que dans un sous-élément de l'élément XML parent. On peut fixer le nom de cet attribut, l'espace de noms auquel il appartient, et imposer qu'il soit présent (attribut required).

3.4.6. Annotation @XmlValue

Une classe donnée ne peut avoir plus d'un champ ou getter annoté par @XmlValue. La présence de cette annotation indique que les instances de cette classe sont représentées par une valeur simple unique, donnée par le champ qui porte cette annotation. Ainsi, lorsque ces objets seront utilisés ailleurs, ils seront représentés par cette valeur simple. Voyons un exemple d'utilisation.

Exemple 14. Utilisation de @XmlValue

// 
 // Classe Salaire
 //
 @XmlAccessorType(XmlAccessType.FIELD)
 public  class Salaire {

     @XmlValue
     private  int montant ;

     // reste de la classe
}

 //
 // Classe Marin
 // 
 @XmlRootElement
 @XmlAccessorType(XmlAccessType.FIELD)
 public  class Marin {

     private Salaire salaire ;

     // reste de la classe
}

Examinons le document XML généré avec ces annotations.

Exemple 15. Utilisation de @XmlValue : document XML généré

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

 <marin>
     <salaire>100</salaire>
 </marin>

Comme on peut le constater, l'élément salaire ne comporte pas de sous-élément, il a été construit à partir de la valeur du champ annoté par @XmlValue.
JAXB et services REST
Retour au blog Java le soir
Cours & Tutoriaux