GUUG e.V.
Antonienallee 1
45279 Essen
kontakt@guug.de
Impressum

Erste Schritte mit JAXB

Martin Schulte

Eifrig ist Sun dabei, Java und XML miteinander zu verzahnen. Eines der neueren entstandenen Produkte ist dabei die Java Architecture for XML Binding, kurz JAXB. Anhand eines "einfachsten" Beispiels zeige ich, wie man sehr einfach sehr viel Arbeit damit sparen kann.

XML und Schema

Sobald ein Datenstrom der XML-Spezifikation entspricht, also - wie man sagt - wohlgeformt ist, kann er von einem XML-Parser gelesen werden.

<?xml version="1.0" encoding="iso-8859-1"?>

<buecherregal
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="buecherregal.xsd">
 <buch titel="Java und XML"      seiten="532"/>
 <buch titel="Java Security"     seiten="480" autor="Scott Oaks"/>
 <buch titel="TCP/IP-Grundlagen" seiten="333"/>
 <erdbeereis mitsahne="true"/>
</buecherregal>

Wie im obigen Beispiel (erdbeereis) ersichtlich, ist die Forderung, dass XML-Dokumente wohlgeformt sein müssen, zwar notwendig, in der Regel für die Weiterverarbeitung aber nicht hinreichend, denn man möchte festlegen, welche Attribute ein Tag haben darf, nach Möglichkeit sogar, welche Wertebereiche für die Attribute erlaubt sind, und natürlich welchen Inhalt ein Tag wiederum haben darf.

Es gibt eine Reihe von Sprachen, mit denen man nun (mehr oder weniger) diese Regeln spezifizieren kann.

Das älteste Mittel dazu ist die Document Type Definition (DTD). Sie ist verhältnismäßig karg, kennt beispielsweise kaum Regeln, mit denen man die Werte von Attributen einschränken kann. Außerdem ist DTD selbst kein XML-Dialekt. In diesen Beziehungen ist XML-Schema wesentlich weiter entwickelt, JAXB "funktioniert" sowohl mit DTDs als auch mit Schemas, auf letztere möchte ich mich im Beispiel beschränken:

<?xml version="1.0" encoding="iso-8859-1"?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

 <xsd:element name="buecherregal">
  <xsd:complexType>
   <xsd:sequence minOccurs="0" maxOccurs="unbounded">
    <xsd:element name="buch"   type="Buch"/>
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>

 <xsd:complexType name="Buch">
  <xsd:attribute name="titel"  type="xsd:string" use="required"/>
  <xsd:attribute name="seiten" type="xsd:int"    use="required"/>
  <!-- use="optional" is default"-->
  <xsd:attribute name="autor"  type="xsd:string"/>
 </xsd:complexType>
</xsd:schema>

Ins Deutsche übersetzt liest sich das etwa so: Ein Bücherregal besteht aus beliebig vielen Objekten von Typ Buch. Jedes Objekt vom Typ Buch hat die Eigenschaften titel, seiten und - optional - autor.

Mit Hilfe eines Schema-validierenen Parsers, die Standardempfehlung lautet hier auf xerces aus dem Apache-XML-Projekt, läßt sich jetzt die Schema-Konformität überprüfen. Ist der xerces (so, dass die jar-files im classpath liegen) installiert, geht das zum Beispiel so:

java sax.Counter -s -v exam1x.xml

und man erhält folgende doch recht gut verständliche Ausgabe:

[Error] exam1x.xml:9:31: cvc-complex-type.2.4.a: Invalid content starting wit
h element 'erdbeereis'. The content must match '(("":buch)){0-UNBOUNDED}'.
Zu beachten ist, dass die Verbindung zwischen Datenstrom und Schema durch das Attribut xsi:noNamespacheSchemaLocation im Wurzelelement des Datenstroms hergestellt wird.

Verarbeitung mit Java

Möchte man nun das "Bücherregal" in einem Java-Programm weiterverarbeiten, so wird man wohl mit folgender Klasse beginnen

public class Buecherregal {
  private String name;
  private java.util.Vector buecher = new java.util.Vector();

  public String getName() { return this.name; }
  public void setName( String name ) { this.name = name; }

  public int anzahlBuecher() { return buecher.size(); }
  public Buch getBuch( int index ) { return (Buch)buecher.get( index ); }
  public void addBuch( Buch b ) { buecher.add( b ); }
}

und analog eine Klasse Buch erstellen.

Wenn nun Objekte dieser Klassen aus einem XML-Datenstrom gefüllt werden sollen, wird man sich in der Regel eines DOM-Parsers bedienen, und den DOM-Baum dann z.B. in einem Konstruktor an die Klassen übergeben.

Die Details dazu findet man im JAXP, dem Java API for XML-Parsing, das ebenfalls im später erwähnten JWSDP zu finden ist. Ich werde darauf hier nicht näher eingehen, denn bis auf die Installation eines JAXP-konformen Parsers wie dem xerces kann man sich diese aufwendige, eintönige und fehlerträchtige Arbeit mit JAXB ersparen:

JAXB - aktuell ist die Version 1.0 - ist nicht mehr als einzelnes Paket sondern nur noch als Teil des Java Web Services Developer Packs (JWSDP - aktuell Version 1.1) erhältlich und kann wie bei SUN üblich kostenlos heruntergeladen werden. Hübsch, dass noch eine ganze Menge anderer Tools dabei sind, allerdings braucht das selbstentpackende Installationsskript so auch 34 MB. Für die Beispiele hier reicht es, nur den JAXB-Teil zu installieren, dann erspart man sich die Konfiguration des ebenfalls mitgelieferten Tomcat. Ohne X11 habe ich die Installation allerdings nicht ans Laufen bekommen - es ist also durchaus noch etwas Experimentierpotential bei der ganzen Sache.

Die Benutzung des ganzen ist dann recht einfach: Unterhalb des JWSDP-Installationsverzeichnisses gibt es das Script jaxb-1.0/bin/xjc.sh. Dieses Script nimmt im wesentlich ein XML-Schema (oder auch mehrere) und erzeugt die Java-Klassen, die zur Aufnahme entsprechender Daten benötigt werden. Das default-Package für die erzeugten Klassen heißt generated, dies läßt sich mit der Kommandozeilen-Option -p festlegen. Damit die generierten Dateien bei Verwendung von -p nicht mit existierenden kollidieren und beim Aufräumen schnell weggeräumt werden können, empfiehlt es sich mit der -d-Option noch ein Verzeichnis anzugeben, unterhalb dem die Dateien angelegt werden.

Das Einlesen einer XML-Datei in einen Objektbaum (von Sun "unmarshalling" genannt) ist so einfach, dass man eigentlich nicht mehr viele Worte darüber verlieren muß:
package de.guug.buecherregal;

import java.util.Iterator;

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

public class Main {
  public static void main( String[] args )
    throws JAXBException, java.io.IOException
  {
    JAXBContext jc = JAXBContext.newInstance( "de.guug.buecherregal" );
    Unmarshaller u = jc.createUnmarshaller();
    Buecherregal regal =
      (Buecherregal)u.unmarshal( new java.io.FileInputStream( args[0] ) );
    for ( Iterator it = regal.getBuch().iterator(); it.hasNext(); ) {
      Buch buch = (Buch)it.next();
      System.out.println( buch.getTitel()+" hat "+
                          buch.getSeiten()+" Seiten" );
    }
  }
}
Um das ganze lauffähig zu machen, hilft das folgende Skript:
set -e
JWSDP_HOME=/home/ms-data/jwsdp-1.1

JAXB_HOME=$JWSDP_HOME/jaxb-1.0
JAXB_LIB=$JAXB_HOME/lib

if [ -d generated ]; then rm -r generated; fi
if [ -d classes ]; then rm -rf classes; fi

mkdir generated classes

$JAXB_HOME/bin/xjc.sh -d generated -p de.guug.buecherregal buecherregal.xsd

JAXB_JAR=$JAXB_LIB/jaxb-api.jar:$JAXB_LIB/jaxb-libs.jar:$JAXB_LIB/jaxb-ri.jar

javac -d classes -classpath $JAXB_JAR `find src generated -name '*.java'`
cp generated/de/guug/buecherregal/jaxb.properties classes/de/guug/buecherregal
java -classpath classes:$JAXB_JAR de.guug.buecherregal.Main exam1.xml
Das einzige, was nicht mehr oder weniger zwangsläufig aus dem bisher gesagten folgt, ist das Kopieren der generierten Datei jaxb.properties in den classes-Baum.

Was noch?

Ähnlich einfach wie das Einlesen erfolgt auch die Ausgabe ("unmarshalling"), ebenso läßt sich der Java-Objektbaum vor der Ausgabe gegen das Schema verifizieren (Im ersten Moment mag es unklar erscheinen, wozu das überhaupt sinnvoll ist: Im Schema können aber durchaus mehr Restriktionen hinterlegt sein, als in den Java-Klassen erkenntlich, beispielsweise kann in Schema festgelegt werden, dass ein Objekt 3 bis 7-mal vorkommen darf.)

Eine Ant-Task ist auch bereits enthalten, eine entsprechende build.xml befindet sich zusammen mit den anderen hier gezeigten Dateien auf GUUG-Webserver.

Wenn das Standardverhalten des JAXB nicht reicht, läßt er sich sehr weitreichend konfigurieren (natürlich über eine XML-Datei), ein sinnvolles Beispiel könnte hier sein, dass die Seitenzahl in unserem obigen Beispiel nicht in einem int sondern in einem Integer gespeichert werden sollen.

Fazit

Ich habe beim Rewrite eines kleinen Programms (1.000 Zeilen) die Eingabe-Dateien von CSV auf XML umgestellt. Dabei hat sich JAXB extrem bewährt. Der geringe Zusatzaufwand für das Erstellen des Schemas (der sich schon praktisch dadurch bewährt hat, dass man nun die Eingabe wesentlich besser verifizieren kann) wurde durch das einfachere Handling mehr als kompensiert, man denke nur an den Aufwand, den das Hinzufügen eines (optionalen) Feldes in CSV-Dateien macht.

Andererseits bringt, wie man auf der Mailingliste JAXB-INTEREST@JAVA.SUN.COM verfolgen kann, der Speicherhunger von JAXB bei großen Eingabeströmen wohl auch etliche Probleme mit sich, deshalb wird nach Aussage der mitdiskutierenden Entwickler auch bereits am lazy-binding gearbeitet. Zudem, so deute ich die Aussagen in der Liste, ist wohl auch wesentlich schwieriger, wenn man von einem fertigen XML ausgeht, als wenn man, wie im Fall meiner Anwendung, erst in Java denkt und sich dann ein passendes XML konstruiert.

Veranstaltungen
FFG2017
Frühjahrsfachgespräch 2017
21. bis 24. März 2017 an der TU Darmstadt
Kalender
18.November 2017
KWMoDiMiDoFrSaSo
45  6 7 8 9 10 11 12
46  13 14 15 16 17 18 19
47  20 21 22 23 24 25 26
48  27 28 29 30 1 2 3
49  4 5 6 7 8 9 10
50  11 12 13 14 15 16 17
GUUG News