JAXB

Frissítve: 2017. november 18.

Mostanában, ha XML-t kell használnom Javaból, először mindig a JAXB (Java Architecture for XML Binding) jut eszembe, ami egy nagyon egyszerűen, gyorsan használatba vehető binding framework, melynek feladata a Java objektumok és az XML elemek egymáshoz rendelése, egymással való megfeleltetése. Magasabb absztrakciós szintet képvisel, mint a SAX, vagy a DOM, hiszen itt gyakorlatilag a programból csak Java objektumokkal kell dolgoznunk, az oda és vissza alakítást elvégzi a keretrendszer. Hasonló az ORM-hez, csak ott a feladat Java objektumok relációs adatbázis elemekké (sorokká) alakítása.

A binding és a mapping szavakat általában keverik, nincs is kiforrott definíció, azonban érdemes megfontolnunk Mark D. Hansen SOA Using Web Services című könyében az általa kifejlesztett terminológiát. Szerinte a binding esetén az XML séma egy megvalósulása a belőle képzett Java osztály, míg mapping esetén a Java osztályt például az üzleti logikánkban használjuk, csak egy megjelenési formája, hogy XML-be is mentjük, vagy onnan betöltjük, esetleg adatbázisba perzisztáljuk. Emiatt szerinte a JAXB egy binding framework.

A JAXB egy szabvány (JSR-222, melynek a referencia implementációja a stílusosan csak JAXB Reference Implementation-nek hívott projekt, mely már a JDK része is. A Java 8-ba a JAXB 2.2 verziója került (ez könnyen ellenőrizhető a xjc -version paranccsal).

Fejlesztésnél választhatjuk azt, hogy a Java kódból indulunk ki, és abból generáljuk le az XML sémát a séma generátor (schema generator) segítségével. Vagy kiindulhatunk az XML sémából, és abból generáljuk le a Java kódot a séma fordító (schema compiler) segítségével. A kettő közötti megfeleltetést Java annotációkkal lehet konfigurálni, amiket az előbbi esetben magunk írunk, az utóbbi esetben a séma fordító generálja le az osztályokba. Szerencsére az előbbi esetben viszonylag kevés annotációt kell használni, hiszen a JAXB is követi az EJB 3 azon törekvését, hogy az annotációknak alapértelmezett értékeik legyenek, amivel már működik a programunk.

JAXB működése

A legrosszabb eset, ha XML sémánk is van, és annak megfelelő XML dokumentumot kell gyártani, valamint Java osztályaink is vannak, amik valamilyen üzleti logikát valósítanak meg. Ekkor megpróbálhatunk a JAXB-vel trükközni, hogy úgy helyezzük el az annotációkat a Java osztályokon, hogy pont az az XML jöjjön ki, amit szeretnénk (nehezebb, mert nagy JAXB ismeret kell hozzá), vagy az XML sémából kigeneráljuk a Java osztályokat, mint DTO objektumok, és programból alakítgatjuk a business domaint megvalósítandó osztályainkra (redundánsabb a kód). Tehát a sémának az annotált Java osztályok felelnek meg, míg az XML dokumentumnak a Java objektumok. XML dokumentumból Java objektumok az unmarshall művelettel keletkeznek, fordítva a marshall művelet használható.

Binding

Nézzünk is egy egyszerű példát, mely letölthető a GitHub-ról. Adott egy könyvkatalógus (Catalog osztály), és abban könyvek (Book) osztály. A kapcsolat egyirányú, csak a Catalok hivatkozik a Book osztályra.

Ahhoz, hogy XML-lé alakítható legyen, a Catalog osztályra tegyük rá az @XmlRootElement annotációt. A JAXB miatt mindkét osztálynak rendelkeznie kell paraméter nélküli konstruktorral is.

@XmlRootElement
public class Catalog {

    private List<Book> books;

    @XmlElement(name = "book")
    public List<Book> getBooks() {
        return books;
    }

    public void setBooks(List<Book> books) {
        this.books = books;
    }
}

Az @XmlElement annotáció hatására a tag neve nem az attribútum neve lesz, hanem a name paraméterként megadott.

Ahhoz, hogy ezt XML-be mentsük, a marshall műveletet kell meghívni a következőképpen. Először egy JAXBContext-et kell létrehoznunk, és adjuk meg neki az XML-be menteni kívánt osztályokat. Utána létrehozzuk a Marshaller objektumot, majd meghívjuk a marshal metódust.

JAXBContext ctx = JAXBContext.newInstance(Catalog.class, Book.class);
Marshaller marshaller = ctx.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
  Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT,
  Boolean.FALSE);

StringWriter writer = new StringWriter();
marshaller.marshal(catalog, writer);
return writer.toString();

És máris megjelenik a kívánt XML.

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

<catalog>
    <book>
        <isbn10>059610149X</isbn10>
        <title>Java and XML</title>
        <author>Brett McLaughlin, Justin Edelson</author>
        <publisher>O'Reilly Media</publisher>
        <year>2006</year>
    </book>
    <book>
        <isbn10>1590597060</isbn10>
        <title>Pro XML Development with Java Technology</title>
        <author>Ajay Vohra</author>
        <publisher>Apress</publisher>
        <year>2005</year>
    </book>
</catalog>

Ez az alapértelmezett működés, de ezt tetszőleges módon testre szabhatjuk. Például:

  • Ha azt akarjuk, hogy az isbn10 attribútum legyen, tegyük rá a getIsbn10() metódusra az @XmlAttribute annotációt
  • Ha ezt a field-re akarjuk tenni, akkor használjuk osztály szinten a @XmlAccessorType(XmlAccessType.FIELD) annotációt, ez jelzi, hogy nem metódusra akarjuk rakni a többi annotációt
  • Az @XmlType(propOrder={...}) annotációval adhatjuk meg az elemek sorrendjét
  • Az @XmlTransient annotációval megmondhatjuk egy attribútumra, hogy ne mentődjön az XML-be
  • Az @XmlElementWrapper annotációval az ismétlődő elemek köré tehetünk egy wrapper XML tag-et
  • Amennyiben névteret is meg akarunk adni, tegyük ezt a package-info.java állományban a @XmlSchema annotáció segítségével.

Ilyenkor az alapértelmezett binding működik, azaz meg van adva, hogy melyik XML séma típushoz milyen Java típus tartozik, és fordítva. Persze ezt is felüldefiniálhatjuk.

Az XML-bel objektumok visszanyerése hasonlóan egyszerű:

JAXBContext ctx = JAXBContext.newInstance(Catalog.class, Book.class);
Unmarshaller unmarshaller = ctx.createUnmarshaller();
return (Catalog) unmarshaller.unmarshal(source);

A Java forrásból XML sémát a schemagen paranccsal generálhatunk. Amennyiben az XML séma felől szeretnénk elindulni, az xjc parancsot kell kiadnunk, ami legenerálja a Java osztályokat. Használhatjuk a JAXB2 Maven Plugin megfelelő goaljait is.