Aus einem XSD File Java Klassen erzeugen mittels JAXB Maven Plugin

JAXB heisst die Komponente, mit der man XML nach Java umwandeln („unmarshallen“) oder Java Klassen nach XML serialisieren kann („marshallen“).
Gegeben: Ein XSD File, welches eine bestimmte XML Struktur wiedergibt. Das soll in das aktuelle Projekt eingebunden werden, und aus dem XSD sollen von Maven automatisch Java Klassen generiert werden.

1. Java Klassen von Hand erstellen

Die Java Klassen können auch von Hand mit dem Tool xjs aus dem JDK erstellt werden. Dazu einfach das Tool per Commandline aufrufen, das XSD File und den Zielordner angeben und fertig.
  • CMD öffnen (PowerShell ist wie immer zu dumm dafür)
  • Zum JDK bin Ordner navigieren (etwa C:\Program Files\Java\jdk1.8.0_131\bin)
  • xjc.exe -d <Zielordner angeben> beispiel.xsd

2. Java Klassen mittels Maven Plugin erstellen lassen

2.1 JAXB als Maven Plugin eintragen

Hat man ein Maven Projekt mit einem pom.xml kann man das JAXB Plugin dort unter „project -> build -> plugins“ eintragen.
Das Plugin wird dann bei jedem Maven Goal „generate“ bzw. „generate-sources“ ausgeführt und die Java Klassen erstellt. Dies kann man auch manuell machen: Rechtsklick auf das pom.xml und dann „run as“ -> „Maven generate-sources“.
Das XSD kann man etwa in folgenden Ordner kopieren: src/main/resources/xsdordner/
Die Java Klassen werden dann unter „target/generated-sources/xjc“ erstellt und können im Projekt verwendet werden.
PS: Da die Java Klassen dann im Target Ordner lieben bzw. sowieso immer von Maven generiert werden müssen sie z.Bsp. nicht ins GIT committed werden.
Der BindingDirectory nennt den Ordner, wo das binding.xml liegt (siehe nächstes Kapitel).
            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <schemaIncludes>xsdordner/*.xsd</schemaIncludes>
                    <bindingDirectory>src/main/xjb</bindingDirectory>
                    <bindingIncludes>
                        <include>*.xml</include>
                    </bindingIncludes>
                </configuration>
            </plugin>
JAXB wird zum Beispiel auch im XSD definierte Enums entsprechend in den Java Klassen erstellen.
Wichtig: Die Java Klassen dürfen nie manuell bearbeitet werden, da Maven die Änderungen gleich wieder überschreiben würde!

2.2 Datei binding.xml erstellen

Das „binding.xml“ liegt etwa im Ordner „src/main/xjb“ und enthält verschiedene Angaben, wie das XSD in XML umgewandelt werden soll.
Zum Beispiel kann man mit „schemaBindings“ angeben, in welches Package die Klassen erstellt werden sollen. Mit „schemaLocation“ gibt man an, wo das XSD File liegt.
Ein Problem, was ich hatte, war, dass zwei Tags auf unterschiedlichen Ebenen gleich hiessen. Dies kann hier behoben werden, in dem man für ein Tag sagt, es soll einen anderen Klassennamen verwenden. Ansonsten gibt es ein riesiges Chaos, wenn zwei Tags etwa „Personen“ heissen.
Ich hatte etwa folgendes XSD:
<xs:element name="personen">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="freunde"> 
        <xs:complexType>
          <xs:element name="personen">
          </xs:element>
        </xs:complexType>
      </xs:element> 
    </xs:sequence>
  </xs:complexType>
</xs:element>
Das Problem in diesem XSD ist, dass zwei XS-Elemente den Namen „personen“ tragen. Um dem entgegenzuwirken, kann man im bindings.xml folgendes Binding erfassen:
    <jaxb:bindings schemaLocation="../resources/xsdordner/*.xsd" node="/xs:schema">
        <jaxb:schemaBindings>
            <jaxb:package name="ch.meinapp.meinpaket" />
        </jaxb:schemaBindings>
        <jaxb:bindings node="xs:element[@name='personen']">
            <jaxb:class name="PersonenRoot" />
        </jaxb:bindings>
    </jaxb:bindings>
Er macht dann einen XPath Abfrage auf das XML, um das Tag „personen“ zu finden. Dabei findet er nur das äussere Tag, für das inne wäre ja eine XPath Abfrage „personen -> freunde -> personen“ nötig. Für das äussere „personen“ Tag erstellt er dann eine Klasse namens „PersonenRoot“.

2.3 Adapter schreiben für ungünstige Klassen

In einem Attribut wurde ein Datum mit dem Typ „xsd:dateTime“ verwendet. JAXB hat daraus in der Java Klasse automatisch den Typ „XMLGregorianCalendar“ verwendet. XMLGregorianCalendar ist aber schon ziemlich alt und zudem nicht Threadsafe. Darum kann man im binding.xml mittels „globalBinding“ eine alternative Klasse angeben, die er verwenden soll.
    <jaxb:globalBindings>
        <xjc:javaType name="java.time.OffsetDateTime" 
xmlType="xs:dateTime" adapter="ch.meinapp.meinpaket.OffsetDateTimeAdapter" />
        <xjc:simple />
    </jaxb:globalBindings>
Der dazugehörige Adapter für dieses Beispiel mit OffsetDateTime sieht dann so aus:
public class OffsetDateTimeAdapter extends XmlAdapter<String, 
OffsetDateTime> {

private final DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

public OffsetDateTime unmarshal(String lexicalRepresentation) {
    if (lexicalRepresentation == null) {
      return null;
    }
    return formatter.parse(lexicalRepresentation, OffsetDateTime::from);
}

public String marshal(OffsetDateTime dateTime) {
    if (dateTime == null) {
        return null;
    } 

    return formatter.format(dateTime);
    }
}

2.4 Das XSD so aufteilen, dass er mehrere Klassen macht

JAXB orientiert sich am XSD, um zu bestimmen, wieviele Java Klassen er erstellen soll. Das kann man durchaus beeinflussen.
Das folgende XSD würde eine Klasse „Personen“ erstellen mit „Freunde“ als anonyme innere Klasse:
<xs:sequence>
    <xs:element name="personen">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="freunde" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:sequence>
Wenn man das XSD aber folgendermassen aufsplittet, erstellt JAXB eine Klasse „Personen“ und eine Klasse „Freunde“. Man beachte, dass der complexType den gleichen Namen „personen“ wie das xs:element bekommen hat.
<xs:sequence>
     <xs:element name="personen" />
</xs:sequence>

<xs:complexType name="personen">
    <xs:sequence>
        <xs:element name="freunde" />
    </xs:sequence>
</xs:complexType>
Schlussendlich ist JAXB ein bischen Pain in the A** aber es funktioniert!

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s