Kein Parser! Direktes Bearbeiten von XML Daten in Java mit JAXB

Martin Kompf

Die Java Architecture for XML Binding (JAXB) erlaubt die direkte Verarbeitung von XML Daten in einem Java Programm. Der Programmierer muss sich dabei nicht mit XML-Spezifika wie Parser, Transformer oder dem Document Object Model (DOM) auseinandersetzen. Vielmehr stellt JAXB dem Java Programmierer seine gewohnte Sicht auf Klassen und Objekte bereit - die Verarbeitung des XML Datenstroms erfolgt komplett undercover.

Die Artikel XML Stream Reader, SAX Parser oder DOM API zeigen verschieden Methoden der Verabeitung von XML Daten mittels Java. Allen diesen Verfahren ist jedoch ein XML-zentrischer Ansatz gemein: Der Softwareentwickler muss sich mit Dingen wie Streaming, Event-Verarbeitung oder DOM herumschlagen.

Beispiel GPX Datei

So zum Beispiel bei der Verarbeitung von GPX Dateien im Artikel DOM API: Aufgabe war, eine XML Datei im GPX Format einzulesen und aus den darin enthaltenen Wegpunkten eine zusammenhängende Route zu erzeugen. Statt jedoch direkt mit Objekten wie Wegpunkt oder Route zu arbeiten, muss der Entwickler stattdessen wissen, was Elemente, Siblings oder Attribute sind. Dieser gedankliche Spagat frisst Zeit, verhindert Kreativität und ist fehlerträchtig.

XML Schema beschreibt die Daten

JAXB stellt nun genau die notwendige Brücke zwischen Anwendungsdomäne (Routen und Wegpunkte) und technischer Repräsentation (XML) her. Da es für das GPX Format eine exakte technische Beschreibung im Form einer XML Schemadefinition gibt, ist der Aufwand für den Anwender minimal:

Zunächst lädt man sich die GPX Schemadefinition von der Website von Topografix herunter. Daraus lassen sich mittels des im JDK enthaltenen JAXB Binding Compilers xjc direkt alle notwendigen Java Klassen generieren:

xjc -d gen-src -p de.kompf.javaxml.jaxb.xsdfirst.data gpx.xsd

Der Parameter -p bestimmt die Java Package für alle generierten Klassen, -d instruiert xjc den erzeugten Code im Verzeichnis gen-src abzulegen. Es ist eine gute Vorgehensweise, geschriebenen und generierten Code zu trennen - letzterer wird in der Regel nicht in das Sourcecode Verwaltungssystem eingecheckt. Natürlich muss man beim Kompilieren nun darauf achten, dass der Java Compiler Kenntnis vom zusätzlichen Verzeichnis gen-src erhält. Unter Eclipse bewerkstelligt man dies mittels Build path - Use as Source folder.

Lesen und Schreiben

Mit Hilfe der generierten Klassen und der JAXB API ist das Lesen und Schreiben einer GPX Datei nun ein Kinderspiel:

import java.io.*;
import javax.xml.bind.*;
import de.kompf.javaxml.jaxb.xsdfirst.data.*;

public class GpxJaxb {
  public GpxType readGpx(File file) throws JAXBException {
    return JAXB.unmarshal(file, GpxType.class);
  }
  
  public void writeGpx(GpxType gpxFile, File file) throws JAXBException {
    JAXBContext jc = JAXBContext.newInstance(GpxType.class);
    Marshaller m = jc.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    ObjectFactory obf = new ObjectFactory();
    m.marshal(obf.createGpx(gpxFile), file);
  }

Während das Einlesen nur aus einer einzigen Anweisung besteht, sind beim Herausschreiben einige Umstände mehr vonnöten, um eine formatierte XML Ausgabe zu erzielen.

JAXB.unmarshal() liefert ein Objekt vom Typ GpxType zurück, das direkt der fachlichen Anwendungsdomäne entstammt. Methoden wie getWpt und getRte liefern typisierte Listen von Wegpunkten und Routen zurück. Der Anwendungsfall Erzeuge Routen aus Wegpunkten lässt sich damit sofort ohne Kenntnis von XML Technologien implementieren:

  public void createRouteFromWaypoints(GpxType gpxFile) {
    RteType route = new RteType();
    gpxFile.getRte().add(route);
    route.setName("Created from waypoints");
    for (WptType pt : gpxFile.getWpt()) {
      route.getRtept().add(pt);
    }
  }

  public static void main(String[] args) throws Exception {
    GpxJaxb simpleGpxJaxb = new GpxJaxb();
    File file2 = new File("Rue-Mz.gpx");
    GpxType gpxType = simpleGpxJaxb.readGpx(file2);
    simpleGpxJaxb.createRouteFromWaypoints(gpxType);
    File file3 = new File("Rue-Mz-Rte.gpx");
    simpleGpxJaxb.writeGpx(gpxType, file3);
    System.out.println("GPX file created: " + file3.getAbsolutePath());
  }
}

Fazit

Dieses Beispiel demonstriert eindrucksvoll die Mächtigkeit von JAXB, insbesondere wenn man den Umfang und die Klarheit des Sourcecodes mit dem aus DOM API vergleicht.

Der hier vorgestellte Weg folgt dabei dem Schema-first Ansatz: Zuerst existierte das XML Schema, aus dem dann der Binding Compiler alle notwendigen Java Klassen generiert hat. Natürlich ist auch der umgekehrte Weg Java-first möglich, wenn zuerst die Java Klassen vorliegen und kein XML Schema verfügbar ist. Damit befasst sich ser Artikel JAXB: Speichern von Java Objekten als XML.

Es darf hier - neben all den Vorteilen - ein Nachteil von JAXB nicht unerwähnt bleiben: Das Verfahren ist für sehr große Datenmengen nicht geeignet, da alle Daten aus der XML Datei komplett im Hauptspeicher als Java Objekte vorliegen müssen. Hier sind Streaming- oder Event-basierte Verfahren besser geeignet, insbesondere wenn man eigentlich nur einen Subset des XML Datenstroms verarbeiten muss.

Außerdem steht und fällt der Schema-first Ansatz mit der Verfügbarkeit und Qualität des XML Schemas. Wenn dieses voll von untypisierten IDREF oder anyType Deklarationen ist, wird die Verwendung von JAXB kaum einen Vorteil bringen. Vor einer endgültigen Architekturentscheidung für JAXB müssen hier also unbedingt eine Inventarisierung der erforderlichen Schemadefinitionen und Probeläufe der Codegenerierung erfolgen.