JAXB – ještě bližší propojení Javy a XML

Java i XML jsou technologie, které se často používají pro komunikaci mezi aplikacemi na různých operačních systémech. Proto je v Javě řada možností, jak s XML daty pracovat, např. SAX, DOM atd.. Jednou z dalších technologií provazující Javu s XML je i JAXB (Java Architecture for XML Binding), jejíž použití si ukážeme na praktických příkladech. JAXB nabízí metody pro konverzi XML dat na Java objekty a naopak, a umožňuje zápis a čtení XML z mnoha různých zdrojů, například ze souboru, streamu, nebo z URL. V tomto článku si na několika příkladech ukážeme, jak se s JAXB pracuje.

Budeme se zabývat JAXB verze 2.0 a vyšší. Verze 2.0 je kompatibilní s JDK 5 a je součástí JDK 6. Aktuální verze je 2.2 a najdete ji v JDK 7. Všechno co potřebujeme se nachází v balíku javax.xml.bind.

Příklad 1: Převod Java objektu na XML dokument a naopak

Aby mohly být objekty určité třídy transformovatelné do XML dokumentů, tak je minimálně potřeba, aby měla příslušná třída bezparametrický konstruktor a byla anotována anotací @XmlRootElement. Tato anotace označuje, že se objekty dané třídy (či výčtové typy) budou transformovat do XML dokumentu jako XML elementy. Tato anotace může být dále použita ve spojení s anotacemi @XmlType@XmlEnum@XmlAccessorType a@XmlAccessorOrder, jejichž význam lze dohledat například zde.

 

Vzorová Java třída:

package jaxb;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Product {

  private String name;
  private float price;

  public Product() {
  }

  public Product(String name, float price) {
    this.name = name;
    this.price = price;
  }

  ...

}

K dispozici je však mnohem více dalších anotací, například:

...

@XmlElementWapper(name="cars")
@XmlElement(name="car")
private ArrayList<Car> cars;

...

vytvoří po transformaci do XML takovouto strukturu:

<cars>
  <car>
    ...
  </car>
  <car>
    ...
  </car>
  ...
</cars>

Všechny objekty třídy Car v kolekci „cars“ jsou transformováný na elementy <car>...</car> a zabaleny do wraperu <cars>...</cars>.

 

Při samotném převodu vytvoříme nejdříve JAXBContext pomocí metody JAXBContext.newInstance(...). Jako parametr uvádíme třídy, které má kontext rozpoznávat. Uvádíme je pomocí seznamu Class tříd, nebo pomoci Stringu obsahujícího dvojtečkou oddělená jména balíčků s třídami. Potom vytvoříme marshaller, který se stará o převod příslušných Java objektů do XML, podle potřeby ho nastavíme (v příkladu nastavíme formát výstupu) a již můžeme provést samotný převod.

Kód pro převod objektů vzorové třídy Product do XML:

package jaxb;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class Main {

  /**
   * @param args
   * @throws JAXBException
   */
  public static void main(String[] args) {
    try {

      // vytvoříme JAXBContext - jen jednou, je to časově náročnější
      JAXBContext jc = JAXBContext.newInstance(Product.class);

      // Marshaller převádí do XML
      Marshaller m = jc.createMarshaller();

      // nastavení formátování
      m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

      // převod do XML
      m.marshal(new Product("chleba", 20.5f), System.out);

    } catch (JAXBException e) {
      e.printStackTrace();
    }
  }

  ...
}

Pro opačný převod z XML do java objektu budeme postupovat podobně, jen místo marshalleru vytvoříme unmarshaller.

...
Unmarshaller u = jc.createUnmarshaller();
Object element = u.unmarshal(new File("abc.xml"));
...

V tomto případě bychom načítali XML data ze souboru „abc.xml“.

Příklad 2: generování tříd z XML schématu

Často máme na začátku projektu XML schéma, ze kterého musíme vycházet. JAXB nám pomůže vygenerovat java třídy pomocí kompilátoru schémat a dále postupujeme stejně jako v prvním příkladu. Kompilátor je také součástí JDK.

 

Generování tříd ze schématu:

xjc schema.xsd

Chování kompilátoru můžeme řídit pomocí parametrů a pluginů. Například při spuštění xjc schema.xsd -Xsync-methods budou vygenerované metody označené jako synchronized a při spuštění xjc -classpath plugin.jar -Xplugin, kde Xplugin je název pluginu, se použije plugin z dodaného souboru.

Validace

XML můžeme také validovat vůči danému schématu, a to pomocí metody setSchema(Schema schema)zavolané na Marshalleru nebo Unmarshalleru.

Rozšíření

Existuje i mnoho doplňků, například Hyperjaxb3, které nabízí perzistenci pro JAXB objekty (ukládání a načítání z relační databáze).

Shrnutí

Jak vidíte, použití JAXB je velice snadné a pohodlné a stojí proto za to zvážit jeho použití místo SAX nebo DOM přístupu. Výhoda JAXB je především v jeho jednoduchosti. Zároveň je však robustním nástrojem, který poskytuje vývojářům rozsáhlé možnosti. Zde jsme si na několika jednoduchých příkladech ukázali jeho základní možnosti. Bližší a podrobnější informace lze nalézt například na následujících odkazech.

3 komentáře u „JAXB – ještě bližší propojení Javy a XML

  1. Dobrý den,
    chtěl bych se na něco zeptat – zkouším si převod pole objektů do XML pomocí JAXB. Jde pomocí tohoto udělat také XML soubor z více objektů dané třídy?? Nemohu přijít na to, jak udělat, aby se mi do finálního souboru nevypisoval pokaždé (s každým novým záznamem) úvodní řádek s informací o kódování atd… jde to nějak zakázat??

    int pocet = poleZaznamu.length;
    try {
    FileOutputStream fos = new FileOutputStream(nazevXml);
    JAXBContext jc = JAXBContext.newInstance(ZaznamMPM.class);
    Marshaller mar = jc.createMarshaller();
    mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    for (int i = 0; i < pocet; i++) {
    ZaznamMPM zaz = poleZaznamu[i];
    mar.marshal(zaz, fos);
    }
    } catch (JAXBException e) {
    e.printStackTrace();
    } catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
    }

    můžete mi prosím poradit, jestli je nějaká možnost jak řádek „“ vypsat pouze před prvním záznamem a mezi dalšími už ne??

    1. Dobrý den,

      problém je v tom, že voláte marshaller v cyklu. JAXB potom při každém volání vytváří nový XML soubor (proto jsou nad každou položkou metadata). Napadají mě dvě řešení:

      1. Vytvořit třídu, kde bude dané pole záznamu jako členská proměnná, a marshaller volat pro tuto třídu. Tedy něco jako:

      @XmlAccessorType(XmlAccessType.FIELD)
      @XmlRootElement
      @XmlType(name = „wrapper“, propOrder = {
      „list“
      })
      public class Wrapper {

      @XmlElement(nillable = true)
      private List list;

      public Workflow(){}

      //atd.

      2. Druhou možností (asi elegantnější) je použít následující:

      try {
      JAXBContext jc = JAXBContext.newInstance(poleZaznamu.getClass());
      Marshaller m = jc.createMarshaller();
      m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
      m.marshal(new JAXBElement(new QName(„pole“), poleZaznamu.getClass(), poleZaznamu), System.out);
      } catch (JAXBException e) {
      e.printStackTrace();
      }

      Princip je podobný jako v prvním případě, a to „obalit“ pole do elementu a ten pak marshallovat. Výhoda tohoto principu je v tom, že se nemusí psát zvláštní „obalující“ třída.

      Doufám, že Vám to pomohlo.

      Nashledanou

  2. Ano, pomohlo – Udělal jsem si obalovací třídu s wraperem a trochu to celé pozměnil a už to vypisuje v pořádku. Mnohokrát děkuji za radu.
    Nashledanou