JAXB-Beans aus XML-Schema-Datei generieren

Da es für existierende XML-Dateien mühselig ist, die annotierten JavaBeans von Hand aufzubauen, gibt es einen Generator, genannt Java Architecture for XML Binding Compiler, kurz xjc. Er kann von der Kommandozeile, von Maven, aus einem Ant-Skript oder auch von Entwicklungsumgebungen aus aufgerufen werden. Er nimmt eine XML-Schema-Datei und generiert die Java-Klassen und eine ObjectFactory, die als – wie der Name schon sagt – Fabrik für die gemappten Objekte aus den XML-Elementen fungiert.

xjc aufrufen

Im ersten Schritt wechseln wir auf die Kommandozeile und testen entweder, ob das bin-Verzeichnis vom JDK im Suchpfad ist, oder wir wechseln in das bin-Verzeichnis, sodass wir xjc direkt aufrufen können, und folgende Ausgabe erscheint:

$ xjc -help

Verwendung: xjc [-options ...] <schema file/URL/dir/jar> ... [-b <bindinfo>] ...

Wenn dir angegeben wird, werden alle Schemadateien im Verzeichnis kompiliert.

Wenn jar angegeben wird, wird die /META-INF/sun-jaxb.episode-Binding-Datei kompiliert.

Optionen:

  -nv                :  Führt keine strikte Validierung der Eingabeschemas durch

  -extension         :  Lässt Herstellererweiterungen zu - Befolgt die

                        Kompatibilitätsregeln und App E.2 der JAXB-Spezifikation nicht strikt

  -b <file/dir>      :  Gibt externe Bindings-Dateien an (jede <file> muss ihre eigene Option -b haben)

                        Wenn ein Verzeichnis angegeben wird, wird **/*.xjb durchsucht

  -d <dir>           :  Generierte Dateien werden in diesem Verzeichnis gespeichert

  -p <pkg>           :  Gibt das Zielpackage an

  -httpproxy <proxy> :  set HTTP/HTTPS proxy. Format ist [user[:password]@]proxyHost:proxyPort

  -httpproxyfile <f> : Wird wie -httpproxy verwendet, verwendet jedoch das Argument in einer Datei zum Schutz des Kennwortes

  -classpath <arg>   :  Gibt an, wo die Benutzerklassendateien gefunden werden

  -catalog <file>    :  Gibt Katalogdateien zur Auflösung von externen Entity-Referenzen an

                        Unterstützt TR9401, XCatalog und OASIS-XML-Katalogformat.

  -readOnly          :  Generierte Dateien werden im schreibgeschützten Modus gelesen

  -npa               :  Unterdrückt die Generierung von Annotationen auf Packageebene (**/package-info.java)

  -no-header         :  Unterdrückt die Generierung eines Datei-Headers mit Zeitstempel

  -target (2.0|2.1)  :  Verhält sich wie XJC 2.0 oder 2.1 und generiert Code, der keine 2.2-Features verwendet.

  -encoding <encoding> :  Gibt Zeichencodierung für generierte Quelldateien an

  -enableIntrospection :  Aktiviert die ordnungsgemäße Generierung von booleschen Gettern/Settern zur Aktivierung von Bean Introspection-APIs

  -contentForWildcard  :  Generiert Contenteigenschaft für Typen mit mehreren von xs:any abgeleiteten Elementen

  -xmlschema         :  Behandelt Eingabe als W3C-XML-Schema (Standard)

  -relaxng           :  Behandelt Eingabe als RELAX NG (experimentell, nicht unterstützt)

  -relaxng-compact   :  Behandelt Eingabe als RELAX NG-Kompaktsyntax (experimentell, nicht unterstützt)

  -dtd               :  Behandelt Eingabe als XML DTD (experimentell, nicht unterstützt)

  -wsdl              :  Behandelt Eingabe als WSDL und kompiliert enthaltene Schemas (experimentell, nicht unterstützt)

  -verbose           :  Verwendet extra-verbose

  -quiet             :  Unterdrückt die Compilerausgabe

  -help              :  Zeigt diese Hilfemeldung an

  -version           :  Zeigt Versionsinformationen an

  -fullversion       :  Zeigt vollständige Versionsinformationen an







Erweiterungen:

  -Xinject-code      :  inject specified Java code fragments into the generated code

  -Xlocator          :  enable source location support for generated code

  -Xsync-methods     :  generate accessor methods with the 'synchronized' keyword

  -mark-generated    :  mark the generated code as @javax.annotation.Generated

  -episode <FILE>    :  generate the episode file for separate compilation

  -Xpropertyaccessors :  Use XmlAccessType PROPERTY instead of FIELD for generated classes

Eigentlich ist bis auf die Angabe der Schema-Quelle (aus einer Datei oder alternativ die URL) keine weitere Angabe nötig. Es ist aber praktisch, zwei Optionen zu setzen: -p bestimmt das Java-Paket für die generierten Klassen und -d das Ausgabeverzeichnis, in dem der Generator die erzeugten Dateien ablegt.

Bibelvers über eine Bibel-API beziehen

Für ein Beispiel wählen wir eine freie Bibel-API. Die URL sieht so aus:

http://api.preachingcentral.com/bible.php?passage=Jn3:16&version=schlachter

Die Angabe von version ist optional, und bestimmt im gesetzten Fall die Sprache bzw. die Bibel-Ausgabe. Die Seite http://www.4-14.org.uk/xml-bible-web-service-api zeigt den Einsatz der einfachen API, bei der ganz simpel in die URL das Buch, Kapitel und Vers steht. Für unser Beispiel sieht das Ergebnis so aus:

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

<bible>

 <title>Jn3:16</title>

 <range>

  <request>Jn3:16</request>

  <result>John 3:16</result>

  <item>

   <bookname>John</bookname>

   <chapter>3</chapter>

   <verse>16</verse>

   <text>Denn Gott hat die Welt so geliebt, daß er seinen eingeborenen Sohn gab, damit jeder, der an ihn glaubt, nicht verloren gehe, sondern ewiges Leben habe.</text>

  </item>

 </range>

 <time>0.007</time>

</bible>

Für unser Beispiel wollen wir das XML-Dokument nicht von Hand auseinanderpflücken, sondern JAXB soll uns eine gefüllte JavaBean mit allen Informationen liefern. Leider gibt es für dieses einfache XML-Format kein XML-Schema, sodass wir uns mit einem Trick behelfen: wir lassen uns von einem Dienst aus der XML-Datei ein Schema generieren, sodass wir damit zu xjc gehen können. Es gibt im Internet einige freie XML-nach-Schema-Konverter, zum Beispiel http://www.xmlforasp.net/CodeBank/System_Xml_Schema/BuildSchema/BuildXMLSchema.aspx. Das XML-Dokument wird in das Textfeld kopiert und nach dem Knopfdruck „Generate Schema“ folgt:

<?xml version="1.0" encoding="utf-16"?>

<xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <xsd:element name="bible">

    <xsd:complexType>

      <xsd:sequence>

        <xsd:element name="title" type="xsd:string" />

        <xsd:element name="range">

          <xsd:complexType>

            <xsd:sequence>

              <xsd:element name="request" type="xsd:string" />

              <xsd:element name="result" type="xsd:string" />

              <xsd:element name="item">

                <xsd:complexType>

                  <xsd:sequence>

                    <xsd:element name="bookname" type="xsd:string" />

                    <xsd:element name="chapter" type="xsd:int" />

                    <xsd:element name="verse" type="xsd:int" />

                    <xsd:element name="text" type="xsd:string" />

                  </xsd:sequence>

                </xsd:complexType>

              </xsd:element>

            </xsd:sequence>

          </xsd:complexType>

        </xsd:element>

        <xsd:element name="time" type="xsd:decimal" />

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>

</xsd:schema>

Diese Ausgabe speichern wir in der Datei bible.xsd, damit sie von xjc verwendet werden kann.

Den Aufruf von xjc setzen wir in eine Batch-Datei:

PATH=%PATH%;C:\Program Files\Java\jdk-9\bin

xjc -d src\main\java -p com.tutego.insel.xml.jaxb.bible bible.xsd

Das Tool generiert alle Typen für den komplexen Typ aus dem XML-Schema, um eine Paket-Annotation festmachen zu können, und ObjectFactory, die einfache Fabrikmethoden enthält, um die gemappten Objekte aufbauen zu können. Die Ausgabe vom Programm ist:

…>xjc -d src\main\java -p com.tutego.insel.xml.jaxb.bible bible.xsd

Ein Schema wird geparst ...

Ein Schema wird kompiliert ...

com\tutego\insel\xml\jaxb\bible\Bible.java

com\tutego\insel\xml\jaxb\bible\ObjectFactory.java

TIPP: In Eclipse lässt sich xjc einfach aufrufen. Selektiere im Package-Explorer BIBLE.xsd, im Kontextmenü Generate • JAXB Classes…, dann wähle das Java-Projekt aus, anschließend Next. Im folgenden Dialog gib als Paket „com.tutego.insel.xml.jaxb.bible“ ein, dann klicke auf Finish. Wichtig! Damit xjc gefunden wird, muss das JDK aktiviert sein, nicht das JRE; nur das JDK bringt das Werkzeug xjc mit.

Dann kann ein Java-Programm den Service mit einer URL ansprechen und sich das Ergebnis-XML in eine JavaBean konvertieren lassen.

package com.tutego.insel.xml.jaxb;




import javax.xml.bind.JAXB;

import com.tutego.insel.xml.jaxb.bible.Bible;




public class BibleOnline {

  public static void main( String[] args ) {

    String url = "http://api.preachingcentral.com/bible.php?passage=Jn3:16&version=schlachter";

    Bible bible = JAXB.unmarshal( url, Bible.class );

    System.out.println( bible.getRange().getItem().getText() );

  }

}

 

WICHTIG Das JAXB-Modul muss in Java 9 über einen Schalter auf der Kommandozeile hinzugenommen werden: –add-modules java.se.ee.

Konflikte in der Schema-Datei *

Leider haben viele XML-Schemas ein Problem, sodass sie nicht direkt vom Schema-Compiler verarbeitet werden können. Ein Beispiel zeigt das Dilemma:

<container>
  <head><content title="Titel"/></head>
  <body><content doc="doc.txt"/></body>
 </container>

In der hierarchischen Struktur heißt das in <head> und <body> vorkommende XML-Element gleich, nämlich content. Die Schema-Datei kann widerspruchslos definieren, dass die beiden XML-Elemente gleich heißen, aber unterschiedliche Attribute erlauben, sozusagen das Head-Content und das Body-Content. Allein durch ihre Hierarchie, also dadurch, dass sie einmal unter head und einmal unter body liegen, sind sie eindeutig bestimmt. Der Schema-Compiler von Java bekommt aber Probleme, da er diese hierarchische Information in eine flache bringt. Er kann einfach eine Klasse Head und Body aufbauen, aber bei <content> steht er vor einem Problem. Da die Schema-Definitionen unterschiedlich sind, müssten zwei verschiedene Java-Klassen unter dem gleichen Namen Content generiert werden. Das geht nicht, und xjc bricht mit Fehlern ab.

Fehler dieser Art gibt es leider häufig, und sie sind der Grund, warum aus vielen Schemas nicht einfach JavaBeans generiert werden können. Erfolglos ohne weitere Einstellungen sind beispielsweise DocBook, Office Open XML, SVG, MathML und weitere. Doch was könnte die Lösung sein? xjc sieht Konfigurationsdateien vor, die das Mapping anpassen können. In diesen Mapping-Dokumenten identifiziert ein XPath-Ausdruck die problematische Stelle und gibt einen Substitutionstyp an. Die Dokumentation auf der JAXB-Homepage weist Interessierte in die richtige Richtung.

JAXB-Plugins

Auf der Webseite https://github.com/javaee/jaxb2-commons gibt es eine Reihe nützlicher zusätzlicher Plugins für JAXB. Zu ihnen gehören:

  • JAXB2 Basic Plugins: Diese Plugin-Sammlung besteht aus Equals (erzeugt die equals(…)-Methode, wobei der Gleichheitstest über ein Equals-Strategie-Objekt austauschbar ist), HashCode (erzeugt hashCode(), wobei auch hier die Berechnung des Hashwerts über ein Strategie-Objekt erfolgt), ToString (erzeugt toString(), auch wieder über ein externes ToStringStrategy-Objekt), Copyable (erzeugt copyTo(…), um Objektzustände in ein anderes typkompatibles Objekt zu kopieren, und auch clone(); arbeitet dabei mit CopyStrategie), Mergeable (realisiert mergeFrom(…)-Methoden mit MergeStrategy-Objekten, die Zustände eines anderen Objekts mit den eigenen vereinen), JAXBIndex (generiert eine index-Datei, die alle generierten Schema-Klassen auflistet), Inheritance und AutoInheritance (erlauben JAXB-Beans, globale Elemente oder komplexe Typen von gewünschten Basisklassen zu erben oder Schnittstellen zu implementieren), Wildcard (spezifiziert, wie sich das Wildcard-Property verhalten soll), Setters (etwas andere Setter-Implementierung bei Sammlungen) und Simplify Plugin (komplexe Properties in Einzel-Properties aufspalten).
  • Value-Constructor Plugin: Jede JavaBean bekommt von xjc nur einen Standard-Konstruktor. Das Plugin gibt einen weiteren Konstruktor hinzu, der alle Attribute direkt initialisiert.
  • Default Value Plugin: Ein XML-Schema kann mit defaultValue vordefinierte Initialbelegungen für Attribute angeben. xjc ignoriert diese. Das Plugin wertet diese Vorbelegungen aus und initialisiert die Attribute der JavaBean gemäß den Werten.
  • Fluent API Plugin: Anstatt eine Bean nur mit Setter-Aufrufen zu initialisieren, etwa eine Person mit setName(…) und setAge(…), generiert das Fluent API Plugin kaskadierbare Methoden, sodass im Beispiel unserer Person nur new Person().withName(…).withAge(…) zu schreiben ist.

Über Christian Ullenboom

Ich bin Christian Ullenboom und Autor der Bücher ›Java ist auch eine Insel. Einführung, Ausbildung, Praxis‹ und ›Java SE 8 Standard-Bibliothek. Das Handbuch für Java-Entwickler‹. Seit 1997 berate ich Unternehmen im Einsatz von Java. Sun ernannte mich 2005 zum ›Java-Champion‹.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.