JAXB: Speichern von Java Objekten als XML

Martin Kompf

Mittels der Java Architecture for XML Binding (JAXB) ist die Erzeugung von XML Daten aus Java Objekten auf einfache Art und Weise möglich. Die Abbildung aus der Java- in die XML-Domäne wird dabei über einige wenige Annotationen im Quelltext gesteuert; Konfigurationsdateien oder Vorverarbeitungsschritte sind nicht notwendig.

Zuerst XML oder Java?

Der Artikel Kein Parser! Direktes Bearbeiten von XML Daten in Java mit JAXB beschreibt die Erstellung eines XML/Java Bindings unter der Voraussetzung, dass eine XML Schemadefinition in Form einer XSD Datei vorliegt. Man spricht daher von einem Schema-first Ansatz.

Unter Umständen ist die Ausgangssituation jedoch gerade entgegengesetzt: Man hat bereits das Datenmodell in Java implementiert und sucht jetzt nach einem einfachen Weg, daraus XML zu erzeugen.

Beispiel Musikdatenbank

Als Beispiel soll die Java Implementierung einer simplen Musikdatenbank zur Verwaltung von CD- oder Vinylalben dienen: Die Klasse MyMusicCollection enthält eine Liste von Objekten des Typs Album. Objekte dieser Klasse repräsentieren jeweils ein Album und enthalten in unserem einfachen Anwendungsfall nur den Namen des Albums, Künstler und Erscheinungsjahr sowie eine Liste von Title Objekten. Die stehen für jeweils einen Titel des Albums mit laufender Nummer und Namen. UML Diagramm JAXB Java


Eine programmatische Bestückung der Objekte könnte dann folgendermaßen aussehen:

import java.io.File;
import javax.xml.bind.*;
import de.kompf.javaxml.jaxb.javafirst.music.*;

public class MusicDB {
  
  private MyMusicCollection createMyMusic() {
    MyMusicCollection myMusic = new MyMusicCollection();
    Album amlor = new Album("A momentary laps of reason", "Pink Floyd", 1987);
    myMusic.getAlbumList().add(amlor);
    amlor.getTitleList().add(new Title(1, "Signs of Life"));
    amlor.getTitleList().add(new Title(2, "Learning to Fly"));
    amlor.getTitleList().add(new Title(3, "The Dogs of War"));
    // ...
    
    Album storm = new Album("Stormbringer", "Deep Purple", 1974);
    myMusic.getAlbumList().add(storm);
    storm.getTitleList().add(new Title(1, "Stormbringer"));
    storm.getTitleList().add(new Title(2, "Love Don't Mean a Thing"));
    storm.getTitleList().add(new Title(3, "Holy Man"));
    // ...

    return myMusic;
  }

Beans und Annotationen erledigen die Arbeit

Um die beschriebene Objektstruktur mittels JAXB serialisieren zu können, benötigt es nur weniger Vorarbeiten:

Die Klassendefinition von MyMusicCollection sieht dann folgendermaßen aus:

import java.util.*;
import javax.xml.bind.annotation.*;

@XmlRootElement(name="mymusic")
public class MyMusicCollection {

  private List<Album> albumList;

  public List<Album> getAlbumList() {
    if (albumList == null) {
      albumList = new LinkedList<Album>();
    }
    return albumList;
  }

  public void setAlbumList(List<Album> albumList) {
    this.albumList = albumList;
  }  
}

Lesen und Schreiben

Nun kann es auch schon an das Umwandeln der Java Objekte in XML und umgekehrt gehen. Die Methoden dafür sind ähnlich einfach wie in Kein Parser! Direktes Bearbeiten von XML Daten in Java mit JAXB:

public class MusicDB {

// ...

  private void writeMusic(MyMusicCollection music, File file) throws JAXBException {
    JAXBContext jc = JAXBContext.newInstance(MyMusicCollection.class);
    Marshaller m = jc.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    m.marshal(music, file);
  }
  
  private MyMusicCollection readMusic(File file) throws JAXBException {
    return JAXB.unmarshal(file, MyMusicCollection.class);
  }

Zur Veranschaulichung erstellt die beispielhafte Main-Methode eine Musikdatenbank mit zwei Alben und schreibt diese in eine XML Datei. Anschließend wird die Datei wieder eingelesen und der Inhalt zur Kontrolle ausgegeben:

  public static void main(String[] args) throws Exception  {
    MusicDB db = new MusicDB();
    MyMusicCollection myMusic = db.createMyMusic();
    File musicFile = new File("mymusic.xml");
    db.writeMusic(myMusic, musicFile);
    
    MyMusicCollection myMusic2 = db.readMusic(musicFile);
    for (Album album : myMusic2.getAlbumList()) {
      System.out.println(album);
      for (Title title : album.getTitleList()) {
        System.out.println("  " + title);
      }
    }
  }
}

Tuning

Damit ist die Aufgabe eigentlich schon erledigt. Wirft man einen Blick in die XML Datei, zeigt sich vielleicht noch einiges Verbesserungspotential:

<mymusic>
    <albumList>
        <artist>Pink Floyd</artist>
        <name>A momentary laps of reason</name>
        <titleList>
            <number>1</number>
            <name>Signs of Life</name>
        </titleList>
    <!-- usw. -->     

Man sieht, dass die Namen der XML Elemente direkt aus den Attributnamen der Java Beans abgeleitet werden. Da nun MyMusicCollection die Methode getAlbumList() hat, heißt das entsprechende XML Element eben albumList. Soll es nur album heißen, so kann man die Methode getAlbumList() mit der Annotation @XmlElement(name="album") versehen.

Analog ist die Verfahrensweise bei Album, um die Titel in dem XML Element title statt titleList zu verpacken:

import java.util.*;
import javax.xml.bind.annotation.*;

public class Album {

  private String name;
  private String artist;
  private int year;
  private List<Title> titleList;
  
  public Album() {
  }
  public Album(String name, String artist, int year) {
    super();
    this.name = name;
    this.artist = artist;
    this.year = year;
  }
  public String getArtist() {
    return artist;
  }
  public void setArtist(String artist) {
    this.artist = artist;
  }

  // weitere Getter und Setter
  
  @XmlElement(name="title")
  public List<Title> getTitleList() {
    if (titleList == null) {
      titleList = new LinkedList<Title>();
    }
    return titleList;
  }
  public void setTitleList(List<Title> titleList) {
    this.titleList = titleList;
  }
  
  @Override
  public String toString() {
    return name + " - " + artist + " (" + year + ")";
  }
}

Die Implementierung der Klasse Title zeigt, dass man für JAXB durchaus auf Getter und Setter verzichten und direkt die public Membervariablen verwenden kann. Außerdem möchte man der integer Variablen number nicht ein eigenes XML Element spendieren, sondern es stattdessen als XML Attribut ausgeben. Das bewirkt die Annotation @XmlAttribute:

import javax.xml.bind.annotation.*;

public class Title {
  @XmlAttribute
  public int number;
  public String name;
  
  public Title() {
  }

  public Title(int number, String name) {
    this.number = number;
    this.name = name;
  }

  @Override
  public String toString() {
    return number + ": " + name;
  }
}

Das resultierende XML ist nun wesentlich kompakter:

<mymusic>
    <album>
        <artist>Pink Floyd</artist>
        <name>A momentary laps of reason</name>
        <title number="1">
            <name>Signs of Life</name>
        </title>
        <title number="2">
            <name>Learning to Fly</name>
        </title>
    <!-- usw. -->     

Schema gefällig?

Beim Austausch von XML Daten mit Partnern stellt sich die Frage nach einer Beschreibung in Form einer XML Schemadefinition. Diese lässt sich aus den annotierten Javaklassen mittels des Tools schemagen, das Bestandteil des JDK ist, ganz einfach erstellen:

schemagen -cp bin de.kompf.javaxml.jaxb.javafirst.music.MyMusicCollection

Das Tool arbeitet in diesem Fall mit den kompilierten class-Dateien. Den Pfad zu ihnen spezifiziert man per Option -cp. Weiterhin wird der vollständige Klassenname des Toplevel Objekts übergeben.

Fazit

Das Beispiel zeigt, dass auch der Java-first Ansatz ein gangbarer Weg ist, um ein Java- zu XML-Binding zu erstellen. Anwendungsfälle dafür gibt es viele, zum Beispiel kann das erzeugte XML für eine persistente Datenspeicherung im Dateisystem oder die Kommunikation mit anderen Anwendungen über ein Netzwerk verwendet werden. Gegenüber der Verwendung des klassischen binären Serialisierungsapparats von Java ergeben sich hier die Vorteile der besseren Interoparabilität mit anderen Programmiersprachen sowie die Möglichkeit, die Daten manuell lesen und editieren zu können.