JAXB zur XML-Verarbeitung mit Java

+ andere TechDocs
+ XML
+ XSD (Schema)
+ XSL, XSLT, XSL-FO
+


JAXB (Java Architecture for XML Binding) erleichtert Konvertierungen zwischen XML und Java. XML-Binding abstrahiert von der dokumentnahen Verarbeitung von XML-Dateien. Stattdessen werden XML-Strukturen auf Java-Klassenstrukturen gemappt.

Die folgenden Beispiele zu JAXB setzen Kenntnisse voraus, insbesondere zu:



Inhalt

  1. Übersicht zu JAXB (Java Architecture for XML Binding)
  2. Beispiel-XML-Dokument und -Schema-XSD-Datei
  3. Generierung von Java-Code aus einem XSD-Schema (beim Buildprozess, mit xjc)
  4. Generierung eines XSD-Schemas aus Java-Code (beim Buildprozess, mit schemagen)
  5. Unmarshalling eines XML-Dokuments zu Java-Objekten (Parsen zur Laufzeit)
  6. Marshalling von Java-Objekten in ein XML-Dokument (Serialisieren zur Laufzeit)
  7. Parsen großer XML-Dokumente durch Kombination von JAXB mit StAX


Übersicht zu JAXB (Java Architecture for XML Binding)

XML-Binding abstrahiert von der dokumentnahen Verarbeitung von XML-Dateien. Stattdessen werden XML-Strukturen auf Java-Klassenstrukturen gemappt (ähnlich wie beim Datenbank-O/R-Mapping).

Dazu gehören folgende vier Schwerpunkte:

JAXB ist oft das optimale Hilfsmittel zur Kommunikation per XML. Allerdings gibt es eine wichtige Einschränkung: Bei "normaler" Anwendung lädt JAXB das gesammte XML-Dokument in den Hauptspeicher, was bei großen Datenmengen zur Beeinträchtigung anderer Prozesse oder sogar zum OutOfMemoryError führen kann. In solchen Fällen kann es empfehlenswert sein, JAXB mit StAX zu kombinieren.

JAXB 2.0 ist ab Java SE 6 und ab Java EE 5 enthalten.

Siehe auch:
https://jaxb.dev.java.net
http://java.sun.com/javase/6/docs/api/javax/xml/bind/package-summary.html
http://java.sun.com/webservices/docs/2.0/tutorial/doc/JAXBWorks.html
http://java.sun.com/webservices/docs/2.0/tutorial/doc/JAXBUsing.html
Vergleich von JAXB mit Apache XMLBeans, ADB und JiBX



Beispiel-XML-Dokument und -Schema-XSD-Datei

Für die folgenden Programmierbeispiele wird ein Beispiel-XML-Dokument und eine dazu passende XML-Schema-XSD-Datei benötigt.

Speichern Sie folgendes XML-Dokument: Buecher.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<mns:buecher xmlns:mns="http://meinnamespace.meinefirma.de"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://meinnamespace.meinefirma.de Buecher.xsd">

   <autor id="42">
      <name>Hinz</name>
      <ort>Hamburg</ort>
   </autor>
   <autor id="43">
      <name>Kunz</name>
      <ort>Krefeld</ort>
   </autor>

   <verlag id="151">
      <name>Aachener Java-Verlag</name>
      <ort>Aachen</ort>
   </verlag>
   <verlag id="152">
      <name>Bonner XML-Verlag</name>
      <ort>Bonn</ort>
   </verlag>

   <buch autorID="43" verlagID="151">
      <titel>XML mit Java</titel>
   </buch>

</mns:buecher>

Das XML-Instanzdokument enhält Autoren, Verlage und Bücher. Damit bei den Büchern nicht jedesmal alle Daten zum Autor und Verlag wiederholt werden müssen, enthalten die Bücher lediglich Verweise auf die bereits definierten Elemente.

Speichern Sie folgende Schema-XSD-Datei: Buecher.xsd

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://meinnamespace.meinefirma.de"
            xmlns="http://meinnamespace.meinefirma.de">

   <xsd:element name="buecher">
      <xsd:complexType>
         <xsd:sequence>
            <xsd:element name="autor"  type="autorType"  maxOccurs="unbounded" />
            <xsd:element name="verlag" type="verlagType" maxOccurs="unbounded" />
            <xsd:element name="buch"   type="buchType"   maxOccurs="unbounded" />
         </xsd:sequence>
      </xsd:complexType>
      <xsd:key         name="autorKey">
         <xsd:selector xpath="autor" />
         <xsd:field    xpath="@id" />
      </xsd:key>
      <xsd:keyref      name="autorKeyref" refer="autorKey">
         <xsd:selector xpath="buch" />
         <xsd:field    xpath="@autorID" />
      </xsd:keyref>
      <xsd:key         name="verlagKey">
         <xsd:selector xpath="verlag" />
         <xsd:field    xpath="@id" />
      </xsd:key>
      <xsd:keyref      name="verlagKeyref" refer="verlagKey">
         <xsd:selector xpath="buch" />
         <xsd:field    xpath="@verlagID" />
      </xsd:keyref>
   </xsd:element>

   <xsd:complexType name="autorType">
      <xsd:sequence>
         <xsd:element name="name" type="xsd:string" />
         <xsd:element name="ort"  type="xsd:string" minOccurs="0" />
      </xsd:sequence>
      <xsd:attribute  name="id"   type="xsd:long" use="required" />
   </xsd:complexType>

   <xsd:complexType name="verlagType">
      <xsd:sequence>
         <xsd:element name="name" type="xsd:string" />
         <xsd:element name="ort"  type="xsd:string" minOccurs="0" />
      </xsd:sequence>
      <xsd:attribute  name="id"   type="xsd:long" use="required" />
   </xsd:complexType>

   <xsd:complexType name="buchType">
      <xsd:sequence>
         <xsd:element name="titel"    type="xsd:string" />
      </xsd:sequence>
      <xsd:attribute  name="autorID"  type="xsd:long" use="required" />
      <xsd:attribute  name="verlagID" type="xsd:long" use="required" />
   </xsd:complexType>

</xsd:schema>

Erläuterungen zu der Schema-XSD-Datei finden Sie unter XML Schema: Beziehungen mit "key" und "keyref".

Sie können diese beiden Dateien und viele weitere der im Folgenden vorgestellten Dateien auch downloaden.



Generierung von Java-Code aus einem XSD-Schema (beim Buildprozess, mit xjc)

Beim Java-6-JDK wird das Programm xjc.exe mitgeliefert (im bin-Verzeichnis). Damit können Sie aus einer Schema-XSD-Datei Java-Klassen generieren.

Speichern Sie folgende einfache Schema-XSD-Datei: MeinEinfachesPersonSchema.xsd

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <xs:element name="Person">
      <xs:complexType>
         <xs:sequence>
            <xs:element name="Name"    type="xs:string" />
            <xs:element name="Adresse" type="xs:string" minOccurs="0" />
         </xs:sequence>
         <xs:attribute  name="id"      type="xs:long" use="required" />
      </xs:complexType>
   </xs:element>
</xs:schema>

Rufen Sie im Kommandozeilenfenster auf:

xjc MeinEinfachesPersonSchema.xsd

tree /F

type generated\Person.java

Sie erhalten die generierte Java-Klasse Person.java mit folgendem Inhalt (gekürzt):

package generated;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "name", "adresse" })
@XmlRootElement(name = "Person")
public class Person
{
    @XmlElement(name = "Name", required = true)
    protected String name;
    @XmlElement(name = "Adresse")
    protected String adresse;
    @XmlAttribute(required = true)
    protected long   id;

    public String getName()                { return name; }
    public void   setName(String value)    { this.name = value; }
    public String getAdresse()             { return adresse; }
    public void   setAdresse(String value) { this.adresse = value; }
    public long   getId()                  { return id; }
    public void   setId(long value)        { this.id = value; }
}

Normalerweise werden Sie allerdings ein Zielverzeichnis und ein bestimmtes Package vorgeben wollen, zum Beispiel so:

md src

xjc -d src -p de.meinefirma.meinprojekt MeinEinfachesPersonSchema.xsd

tree /F

type src\de\meinefirma\meinprojekt\Person.java

Testen Sie auch kompliziertere Schema-XSD-Dateien. Speichern Sie obige Schema-XSD-Datei Buecher.xsd und führen Sie im Kommandozeilenfenster aus:

xjc -d src -p de.meinefirma.buecherentities Buecher.xsd

dir src\de\meinefirma\buecherentities

Diesmal erhalten Sie mehrere Java-Klassen. Sehen Sie sich die Ergebnis-Java-Klassen an.

Durch "Binding Declarations" können Sie die Generierung des Java-Codes beeinflussen.

Für automatische Builds ist die Einbindung in den Buildvorgang wichtig. Sehen Sie sich hierzu die Einbindung von JAXB in Maven an.



Generierung eines XSD-Schemas aus Java-Code (beim Buildprozess, mit schemagen)

Beim Java-6-JDK wird das Programm schemagen.exe mitgeliefert. Damit können Sie aus Java-Klassen Schema-XSD-Dateien generieren. Allerdings ist die Anwendung nicht immer einfach, da schemagen.exe unter Umständen eine Exception wirft, ohne den Grund verständlich zu erläutern.

Eine ClassCastException können Sie erhalten, wenn Sie in der Java-Klasse zusätzliche Annotationen (z.B. für JPA) verwenden, und diese während des schemagen-Aufrufs nicht im Classpath sind. Falls Sie beispielsweise mit Maven bauen, müssen Sie den Scope entsprechend anpassen. Siehe auch Bug ID 6432333.

Eventuell kann es zu einer NullPointerException kommen, wenn in der Java-Klasse keine @XmlType- oder keine @XmlRootElement-Annotation eingetragen ist.

Eine häufigere Ursache für eine NullPointerException ist beschrieben im Bug ID 6510966: Sie erhalten (z.B. mit JDK 6 Update 18 und auch mit JDK 1.7.0-ea) eine NullPointerException, wenn der Verzeichnispfad zur schemagen.exe-Datei ein Leerzeichen enthält, wie es unter Windows Vista bei der Standard-Java/JDK-Installation üblich ist. Obwohl Windows Vista als Pfad etwas Ähnliches wie C:\Programme\Java\jdk1.6 anzeigt, lautet der tatsächliche Pfad C:\Program Files\Java\jdk1.6. Deshalb wird im folgenden Beispiel das Laufwerk "S:" auf das JDK-Installationsverzeichnis gemappt.

Speichern Sie folgende einfache Java-Klasse: Person.java

public class Person
{
   String name;
   String adresse;
   long   id;

   public String getName()                    { return name; }
   public void   setName( String name )       { this.name = name; }
   public String getAdresse()                 { return adresse; }
   public void   setAdresse( String adresse ) { this.adresse = adresse; }
   public long   getId()                      { return id; }
   public void   setId( long id )             { this.id = id; }
}

Rufen Sie im Kommandozeilenfenster auf (passen Sie den Pfad zum JDK an, und falls bei Ihnen "S:" bereits belegt ist, verwenden Sie bitte einen anderen Buchstaben):

subst S: "C:\Program Files\Java\jdk1.6"

S:bin\schemagen Person.java

type schema1.xsd

Sie erhalten folgende Schema-XSD-Datei:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:complexType name="person">
    <xs:sequence>
      <xs:element name="adresse" type="xs:string" minOccurs="0"/>
      <xs:element name="id"      type="xs:long"/>
      <xs:element name="name"    type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Bitte beachten Sie, dass die so generierte Schema-XSD-Datei im <xs:schema>-XSD-Rootelement lediglich XML-Typen (im Beispiel <xs:complexType>), aber kein XML-Element enthält.

Normalerweise werden Sie zumindest das XML-Rootelement vorgeben wollen. Diese Angabe und weitere Eigenschaften der Schema-XSD-Datei steuern Sie über Mapping Annotations, beispielsweise so:

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "name", "adresse" })
@XmlRootElement(name = "Person")
public class Person
{
   @XmlElement(name = "Name", required = true)
   protected String name;
   @XmlElement(name = "Adresse")
   protected String adresse;
   @XmlAttribute(required = true)
   protected long   id;

   public String getName()                    { return name; }
   public void   setName( String name )       { this.name = name; }
   public String getAdresse()                 { return adresse; }
   public void   setAdresse( String adresse ) { this.adresse = adresse; }
   public long   getId()                      { return id; }
   public void   setId( long id )             { this.id = id; }
}

Jetzt erhalten Sie:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Person">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Name"    type="xs:string"/>
        <xs:element name="Adresse" type="xs:string" minOccurs="0"/>
      </xs:sequence>
      <xs:attribute name="id"      type="xs:long" use="required"/>
    </xs:complexType>
  </xs:element>
</xs:schema>

Bitte beachten Sie, dass die generierte Schema-XSD-Datei diesmal im <xs:schema>-XSD-Rootelement das XML-Rootelement <xs:element name="Person"> enthält.

Sie können auch aus mehreren voneinander abhängigen Klassen eine Schema-XSD-Datei generieren. Um Tipparbeit zu sparen, speichern Sie obige Schema-XSD-Datei Buecher.xsd und führen im Kommandozeilenfenster aus:

md src

xjc -d src -p de.meinefirma.buecherentities Buecher.xsd

dir src\de\meinefirma\buecherentities

Jetzt haben wir mehrere Java-Klassen, darunter Buecher.java, welche die drei Klassen AutorType.java, VerlagType.java und BuchType.java verwendet. Um aus diesen Klassen eine Schema-XSD-Datei zu generieren, rufen Sie auf:

md bin

javac -d bin src/de/meinefirma/buecherentities/*.java

subst S: "C:\Program Files\Java\jdk1.6"

S:bin\schemagen -cp bin src/de/meinefirma/buecherentities/Buecher.java

type schema1.xsd

Bitte beachten Sie, dass diesmal beim schemagen-Kommando der Classpath gesetzt werden muss.

Wenn Sie die generierte schema1.xsd mit der ursprünglichen Buecher.xsd vergleichen, werden Sie eine große Ähnlichkeit feststellen.



Unmarshalling eines XML-Dokuments zu Java-Objekten (Parsen zur Laufzeit)

Während die Code-Generierung mit xjc bzw. die Schema-Generierung mit schemagen zur Buildzeit ausgeführt wird, erfolgen Marshalling und Unmarshalling zur Laufzeit:

Für das Unmarshalling-Beispiel werden wieder die bereits bekannte Schema-XSD-Datei und die daraus generierten Java-Klassen verwendet. Speichern Sie obige Schema-XSD-Datei Buecher.xsd und führen Sie im Kommandozeilenfenster aus:

md src

xjc -d src -p de.meinefirma.buecherentities Buecher.xsd

dir src\de\meinefirma\buecherentities

md src\de\meinefirma\main

Speichern Sie im src\de\meinefirma\main-Unterverzeichnis folgende Java-Klasse: BuecherUnmarshaller.java

package de.meinefirma.main;

import javax.xml.bind.JAXBException;
import org.xml.sax.SAXException;
import de.meinefirma.buecherentities.*;

public class BuecherUnmarshaller
{
   public static void main( String[] args ) throws JAXBException, SAXException
   {
      if( args.length != 2 ) {
         System.out.println( "\nBitte Buecher-XSD-Schema und Buecher-XML-Dokument angeben, z.B.:\n" +
                             "java de.meinefirma.main.BuecherUnmarshaller Buecher.xsd Buecher.xml" );
         return;
      }
      Buecher buecher = JaxbMarshalUnmarshalUtil.unmarshal( args[0], args[1], Buecher.class );
      zeigeBuecher( buecher );
   }

   static void zeigeBuecher( Buecher buecher )
   {
      System.out.println( "Autoren:" );
      for( AutorType a : buecher.getAutor() ) {
         System.out.println( "  Id:   " + a.getId() );
         System.out.println( "  Name: " + a.getName() );
         System.out.println( "  Ort:  " + a.getOrt() );
      }
      System.out.println( "Verlage:" );
      for( VerlagType v : buecher.getVerlag() ) {
         System.out.println( "  Id:   " + v.getId() );
         System.out.println( "  Name: " + v.getName() );
         System.out.println( "  Ort:  " + v.getOrt() );
      }
      System.out.println( "Buecher:" );
      for( BuchType b : buecher.getBuch() ) {
         System.out.println( "  AutorID:  " + b.getAutorID() );
         System.out.println( "  VerlagID: " + b.getVerlagID() );
         System.out.println( "  Titel:    " + b.getTitel() );
      }
   }
}

Speichern Sie im src\de\meinefirma\main-Unterverzeichnis folgende Java-Klasse: JaxbMarshalUnmarshalUtil.java

package de.meinefirma.main;

import java.io.File;
import java.text.DecimalFormat;
import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.validation.*;
import org.xml.sax.SAXException;

/** Hilfsmethoden fuer JAXB-Marshalling und -Unmarshalling */
public class JaxbMarshalUnmarshalUtil
{
   static final DecimalFormat DF_2 = new DecimalFormat( "#,##0.00" );

   public static <T> T unmarshal( String xsdSchema, String xmlDatei, Class<T> clss )
   throws JAXBException, SAXException
   {
      // Schema und JAXBContext sind multithreadingsicher ("thread safe"):
      SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
      Schema        schema        = ( xsdSchema == null || xsdSchema.trim().length() == 0 )
                                    ? null : schemaFactory.newSchema( new File( xsdSchema ) );
      JAXBContext   jaxbContext   = JAXBContext.newInstance( clss.getPackage().getName() );
      return unmarshal( jaxbContext, schema, xmlDatei, clss );
   }

   public static <T> T unmarshal( JAXBContext jaxbContext, Schema schema, String xmlDatei, Class<T> clss )
   throws JAXBException
   {
      // Unmarshaller ist nicht multithreadingsicher:
      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      unmarshaller.setSchema( schema );
      return clss.cast( unmarshaller.unmarshal( new File( xmlDatei ) ) );
   }

   public static void marshal( String xsdSchema, String xmlDatei, Object jaxbElement )
   throws JAXBException, SAXException
   {
      SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
      Schema        schema        = ( xsdSchema == null || xsdSchema.trim().length() == 0 )
                                    ? null : schemaFactory.newSchema( new File( xsdSchema ) );
      JAXBContext   jaxbContext   = JAXBContext.newInstance( jaxbElement.getClass().getPackage().getName() );
      marshal( jaxbContext, schema, xmlDatei, jaxbElement );
   }

   public static void marshal( JAXBContext jaxbContext, Schema schema, String xmlDatei, Object jaxbElement )
   throws JAXBException
   {
      Marshaller marshaller = jaxbContext.createMarshaller();
      marshaller.setSchema( schema );
      marshaller.setProperty( Marshaller.JAXB_ENCODING, "ISO-8859-1" );
      marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
      marshaller.marshal( jaxbElement, new File( xmlDatei ) );
   }

   /** Die main()-Methode ist nur fuer Testzwecke */
   public static void main( String[] args ) throws JAXBException, SAXException, ClassNotFoundException
   {
      if( args.length != 3 ) {
         System.out.println( "\nBitte XSD-Schema, XML-Dokument und Zielklasse angeben." );
         return;
      }
      System.out.println( "\nSchema: " + args[0] + ", XML-Dokument: " + args[1] + ", Zielklasse: " + args[2] + "\n" );

      // Unmarshalling-Test:
      long startSpeicherverbrauch = ermittleSpeicherverbrauch();
      long startZeit = System.nanoTime();
      Object obj = unmarshal( args[0], args[1], Class.forName( args[2] ) );
      String dauer = ermittleDauer( startZeit );
      String speicherverbrauch = formatiereSpeichergroesse( ermittleSpeicherverbrauch() - startSpeicherverbrauch );
      System.out.println( "Parsingspeicherverbrauch = " + speicherverbrauch + ", Parsingdauer = " + dauer );
      System.out.println( obj.getClass() );
      // Die folgende Ausgabe macht nur Sinn, wenn es eine sinnvolle toString()-Methode gibt:
      System.out.println( obj );

      // Marshalling-Test:
      startZeit = System.nanoTime();
      marshal( args[0], args[1] + "-output.xml", obj );
      dauer = ermittleDauer( startZeit );
      System.out.println( "\n'" + args[1] + "-output.xml' erzeugt in " + dauer + "." );
   }

   static String ermittleDauer( long startZeitNanoSek )
   {
      long dauerMs = (System.nanoTime() - startZeitNanoSek) / 1000 / 1000;
      if( dauerMs < 1000 ) return "" + dauerMs + " ms";
      return DF_2.format( dauerMs / 1000. ) + " s";
   }

   static long ermittleSpeicherverbrauch()
   {
      System.gc();
      System.gc();
      return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
   }

   static String formatiereSpeichergroesse( long bytes )
   {
      if( bytes < 0 ) return "0 Byte";
      if( bytes < 1024 ) return "" + bytes + " Byte";
      double b = bytes / 1024.;
      if( b < 1024. ) return DF_2.format( b ) + " KByte";
      return DF_2.format( b / 1024. ) + " MByte";
   }
}

Jetzt können Sie mit BuecherUnmarshaller beliebige dem Buecher.xsd-Schema entsprechende XML-Dokumente in Java-Objekte einlesen (und mit JaxbMarshalUnmarshalUtil auch andere).

Speichern Sie beispielsweise obiges XML-Dokument Buecher.xml im Projektverzeichnis. Ihr Projektverzeichnis sieht jetzt so aus (überprüfen Sie es mit tree /F):

[\MeinWorkspace\JAXB]
 |- [src]
 |   '- [de]
 |       '- [meinefirma]
 |           |- [buecherentities]
 |           |   |- AutorType.java
 |           |   |- BuchType.java
 |           |   |- Buecher.java
 |           |   |- ObjectFactory.java
 |           |   |- package-info.java
 |           |   '- VerlagType.java
 |           '- [main]
 |               |- BuecherUnmarshaller.java
 |               '- JaxbMarshalUnmarshalUtil.java
 |- Buecher.xml
 '- Buecher.xsd

Führen Sie im Kommandozeilenfenster aus:

cd \MeinWorkspace\JAXB

tree /F

md bin

javac -d bin src/de/meinefirma/buecherentities/*.java

javac -d bin -cp bin src/de/meinefirma/main/*.java

java -cp bin de.meinefirma.main.BuecherUnmarshaller Buecher.xsd Buecher.xml

Sie erhalten:

Autoren:
  Id:   42
  Name: Hinz
  Ort:  Hamburg
  Id:   43
  Name: Kunz
  Ort:  Krefeld
Verlage:
  Id:   151
  Name: Aachener Java-Verlag
  Ort:  Aachen
  Id:   152
  Name: Bonner XML-Verlag
  Ort:  Bonn
Buecher:
  AutorID:  43
  VerlagID: 151
  Titel:    XML mit Java

Falls Sie keine Validierung wollen, können Sie das Marshalling und Unmarshalling auch ohne XSD-Schema durchführen. In diesem Fall lassen Sie die beiden setSchema()-Aufrufe in der JaxbMarshalUnmarshalUtil-Klasse weg.



Marshalling von Java-Objekten in ein XML-Dokument (Serialisieren zur Laufzeit)

Das folgende Marshalling-Beispiel baut auf das vorherige Unmarshalling-Beispiel auf. Ergänzen Sie im src\de\meinefirma\main-Unterverzeichnis folgende Java-Klasse: BuecherMarshaller.java

package de.meinefirma.main;

import javax.xml.bind.JAXBException;
import org.xml.sax.SAXException;
import de.meinefirma.buecherentities.*;

public class BuecherMarshaller
{
   public static void main( String[] args ) throws JAXBException, SAXException
   {
      if( args.length != 2 ) {
         System.out.println( "\nBitte Buecher-XSD-Schema und Buecher-XML-Zieldateiname angeben." );
         return;
      }
      JaxbMarshalUnmarshalUtil.marshal( args[0], args[1], erzeugeBuecherObjekt() );
      System.out.println( "\n" + args[1] + " erzeugt." );
   }

   static Buecher erzeugeBuecherObjekt()
   {
      Buecher buecher = new Buecher();

      AutorType a = new AutorType();
      a.setId(   42 );
      a.setName( "Otto" );
      a.setOrt(  "Hamburg" );
      buecher.getAutor().add( a );

      VerlagType v = new VerlagType();
      v.setId(   4711 );
      v.setName( "Elefanten-Verlag" );
      v.setOrt(  "Berlin" );
      buecher.getVerlag().add( v );

      BuchType b = new BuchType();
      b.setAutorID(  a.getId() );
      b.setVerlagID( v.getId() );
      b.setTitel(    "Meine Elefanten" );
      buecher.getBuch().add( b );

      return buecher;
   }
}

Führen Sie im Kommandozeilenfenster aus:

cd \MeinWorkspace\JAXB

javac -d bin -cp bin src/de/meinefirma/main/*.java

java -cp bin de.meinefirma.main.BuecherMarshaller Buecher.xsd Buecher-output.xml

type Buecher-output.xml

Sie erhalten:

<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<ns2:buecher xmlns:ns2="http://meinnamespace.meinefirma.de">
    <autor id="42">
        <name>Otto</name>
        <ort>Hamburg</ort>
    </autor>
    <verlag id="4711">
        <name>Elefanten-Verlag</name>
        <ort>Berlin</ort>
    </verlag>
    <buch verlagID="4711" autorID="42">
        <titel>Meine Elefanten</titel>
    </buch>
</ns2:buecher>

Sie können auch direkt die generischen Hilfsmethoden aus der JaxbMarshalUnmarshalUtil-Klasse verwenden und beispielsweise in einem Aufruf sowohl ein JAXB-Unmarshalling als auch ein -Marshalling ausführen, zum Beispiel so:

java -cp bin de.meinefirma.main.JaxbMarshalUnmarshalUtil Buecher.xsd Buecher.xml de.meinefirma.buecherentities.Buecher

Anschließend können Sie die ursprüngliche Buecher.xml mit der erzeugten Buecher.xml-output.xml vergleichen.

Die generischen Hilfsmethoden aus der JaxbMarshalUnmarshalUtil-Klasse funktionieren auch mit anderen Schemas und Java-Klassen. Speichern Sie beispielsweise Adr5.xml und AdrSchemaMitUniqueUndEnum.xsd und rufen Sie auf:

xjc -d src -p de.meinefirma.adressenentities AdrSchemaMitUniqueUndEnum.xsd

javac -d bin src/de/meinefirma/adressenentities/*.java

java -cp bin de.meinefirma.main.JaxbMarshalUnmarshalUtil AdrSchemaMitUniqueUndEnum.xsd Adr5.xml de.meinefirma.adressenentities.Adressen

type Adr5.xml-output.xml



Parsen großer XML-Dokumente durch Kombination von JAXB mit StAX

Bei "normaler" Anwendung lädt JAXB das gesammte XML-Dokument in den Hauptspeicher, was bei großen Datenmengen zur Beeinträchtigung anderer Prozesse oder zum OutOfMemoryError führen kann. Für solche Fälle kann JAXB mit StAX kombiniert werden. Dabei wird nicht das XML-Rootelement mit JAXB geparst, sondern per StAX werden die Unterelemente des Rootelements gesucht und einzeln mit JAXB geparst. Das hilft natürlich nicht in allen Fällen, aber oft gibt es lange Listen sich wiederholender Unterelemente und dann lohnt es sich sehr, wie das folgende Beispiel zeigt.

Informationen zu StAX finden Sie unter StAX (Streaming API for XML) und StAX-Programmierbeispiele.

Für das folgende Beispiel soll wieder obige Schema-XSD-Datei Buecher.xsd verwendet werden. Um die dazu passenden Java-Klassen zu generieren, führen Sie wieder im Kommandozeilenfenster aus:

md src

xjc -d src -p de.meinefirma.buecherentities Buecher.xsd

dir src\de\meinefirma\buecherentities

md src\de\meinefirma\main

Leider sind die drei generierten Java-Klassen AutorType, BuchType und VerlagType nicht direkt für die StAX/JAXB-Kombination geeignet. Wir benötigen die Annotation, dass nicht nur die Buecher-Klasse als JAXB-XML-Rootelement verwertbar sein soll, sondern auch die anderen drei Klassen. Dazu fügen wir in den drei Klassen jeweils vor der "public class ..."-Zeile ein:

@javax.xml.bind.annotation.XmlRootElement( namespace = "", name = "autor" ) in AutorType.java
@javax.xml.bind.annotation.XmlRootElement( namespace = "", name = "buch" ) in BuchType.java
@javax.xml.bind.annotation.XmlRootElement( namespace = "", name = "verlag" )in VerlagType.java

(Alternativ könnte man auch die Schema-XSD-Datei so umbauen, dass diese Annotationen automatisch bei der Klassengenerierung eingefügt werden, z.B. indem man statt des eigentlich besseren Venetian Blind Design das Salami Slice Design verwendet.)

Speichern Sie im src/de/meinefirma/main-Verzeichnis folgende Java-Klasse: XmlParserMitStaxUndJaxb.java

package de.meinefirma.main;

import java.io.*;
import java.text.DecimalFormat;
import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import javax.xml.validation.Validator;
import org.xml.sax.SAXException;

/** Hilfsmethoden zum Parsen grosser XML-Dateien (durch Kombination von JAXB mit StAX) */
public class XmlParserMitStaxUndJaxb
{
   public static long parseXmlElemente( String xsdSchema, String xmlDatei, String packageName,
         ElementeVerarbeitung elemVerarb, boolean printInfo ) throws Exception 
   {
      // Validierung:
      if( xsdSchema != null && xsdSchema.trim().length() > 0 ) {
         validate( xsdSchema, xmlDatei );
      }
      // StAX:
      EventFilter startElementFilter = new EventFilter() {
         @Override public boolean accept( XMLEvent event ) {
            return event.isStartElement();
         }
      };
      long            anzahlElem   = 0;
      XMLInputFactory staxFactory  = XMLInputFactory.newInstance();
      XMLEventReader  staxReader   = staxFactory.createXMLEventReader( new FileReader( xmlDatei ) );
      XMLEventReader  staxFiltRd   = staxFactory.createFilteredReader( staxReader, startElementFilter );
      StartElement    rootElement  = staxFiltRd.nextEvent().asStartElement();
      // JAXB:
      JAXBContext     jaxbContext  = JAXBContext.newInstance( packageName );
      Unmarshaller    unmarshaller = jaxbContext.createUnmarshaller();
      // Parsing:
      if( printInfo )
         System.out.println( "\nSchema: " + xsdSchema + ", XML-Dokument: " + xmlDatei +
                             ", XML-RootElement: " + rootElement.getName().getLocalPart() + "\n" );
      while( staxFiltRd.peek() != null ) {
         Object element = unmarshaller.unmarshal( staxReader );
         Object result  = elemVerarb.verarbeite( element );
         if( printInfo ) System.out.println( result );
         anzahlElem++;
      }
      return anzahlElem;
   }

   public static void validate( String xsdSchema, String xmlDokument ) throws SAXException, IOException 
   {
      SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
      Schema        schema = schemaFactory.newSchema( new File( xsdSchema ) );
      Validator     validator = schema.newValidator();
      validator.validate( new StreamSource( new File( xmlDokument ) ) );
   }

   static String ermittleDauer( long startZeitNanoSek )
   {
      final DecimalFormat DF_2 = new DecimalFormat( "#,##0.00" );
      long dauerMs = (System.nanoTime() - startZeitNanoSek) / 1000 / 1000;
      if( dauerMs < 1000 ) return "" + dauerMs + " ms";
      return DF_2.format( dauerMs / 1000. ) + " s";
   }

   /** Die main()-Methode ist nur fuer Testzwecke */
   public static void main( String[] args ) throws Exception
   {
      if( args.length != 3 ) {
         System.out.println( "\nBitte XSD-Schema, XML-Dokument und den Package-Namen der Zielklassen angeben, z.B.:\n" +
                             "java XmlParserMitStaxUndJaxb MeinSchema.xsd MeineXmlDatei.xml de.meinefirma.meinpackage" );
         return;
      }
      long startZeit = System.nanoTime();
      long anzahlElemente = parseXmlElemente( args[0], args[1], args[2],
                                              new DefaultElementeVerarbeitungImpl(), true );
      String dauer = ermittleDauer( startZeit );
      System.out.println( "\nElementeanzahl: " + anzahlElemente + ", Parsingdauer: " + dauer + "\n" );
   }
}

class DefaultElementeVerarbeitungImpl implements ElementeVerarbeitung
{
   @Override public Object verarbeite( Object element )
   {
      try {
         // ... Hier die eigentliche Verarbeitung der Elemente einfuegen (z.B. Speicherung in Datenbank) ...
         // Fuer Testzwecke:
         // Falls commons-beanutils-1.8.2.jar und commons-logging-1.1.1.jar im Classpath:
         // return new TreeMap<String,Object>( PropertyUtils.describe( element ) );
         // Sonst:
         return element.getClass().getName();
      } catch( Exception ex ) {
         throw new RuntimeException( ex );
      }
   }
}

interface ElementeVerarbeitung
{
   Object verarbeite( Object element );
}

Das XML-Dokument enthält eine Liste von Autoren, eine Liste von Verlagen und eine Liste von Büchern. Per StAX werden die einzelnen Listenelemente gefunden. Die jeweiligen Elemente werden dann als Ganzes per JAXB in Java-Objekte eingelesen (und können dann beliebig weiterverarbeitet werden, z.B. in eine Datenbank gespeichert werden).

Die im Sourcecode beispielhaft eingesetzte DefaultElementeVerarbeitungImpl zeigt lediglich die Klassennamen der geparsten Elemente an (bzw. wenn Sie "PropertyUtils.describe( element )" aktivieren, auch den Inhalt). Hier müssten Sie die weitere Verarbeitungslogik hinzufügen (bzw. eine eigene das Interface ElementeVerarbeitung implementierende Klasse verwenden).

Bei solchen StAX/JAXB-Kombinationen sollten Sie keinesfalls die Validierung im JAXB-Unmarshaller verwenden (per unmarshaller.setSchema( schema )), weil hier durch die häufigen Aufrufe zu viel Performance durch den Overhead verloren ginge. Stattdessen erfolgt die Validierung zu Beginn durch einen einzigen einmaligen Aufruf für das gesammte XML-Dokument (per validate(xsdSchema,xmlDatei)).

Wenn Sie genau wissen, welche Klassen geparst werden müssen, könnten Sie den JAXBContext statt über den Packagenamen mit JAXBContext.newInstance( packageName ) alternativ auch über eine Auflistung der zu parsenden Klassen erzeugen, zum Beispiel so:

JAXBContext jaxbContext = JAXBContext.newInstance( AutorType.class, VerlagType.class, BuchType.class );

Falls Sie nur eine einzige Klasse parsen müssten (was in diesem Beispiel nicht der Fall ist), gäbe es noch eine dritte Alternative: Sie könnten diese eine Klasse beim unmarshal()-Aufruf übergeben, beispielsweise so:

JAXBElement<MeineKlasse> element = unmarshaller.unmarshal( staxReader, MeineKlasse.class );

Diese Variante hätte den Vorteil, dass Sie in der automatisch generierten Elemente-Klasse nicht den XmlRootElement-Zusatz "@javax.xml.bind.annotation.XmlRootElement( namespace = "", name = "..." )" hinzufügen müssten.

Ihr Projektverzeichnis sieht jetzt so aus (überprüfen Sie es mit tree /F):

[\MeinWorkspace\JAXB]
 |- [src]
 |   '- [de]
 |       '- [meinefirma]
 |           |- [buecherentities]
 |           |   |- AutorType.java
 |           |   |- BuchType.java
 |           |   |- Buecher.java
 |           |   |- ObjectFactory.java
 |           |   |- package-info.java
 |           |   '- VerlagType.java
 |           '- [main]
 |               '- XmlParserMitStaxUndJaxb.java
 |- Buecher.xml
 '- Buecher.xsd

Führen Sie im Kommandozeilenfenster aus:

cd \MeinWorkspace\JAXB

tree /F

md bin

javac -d bin src/de/meinefirma/buecherentities/*.java

javac -d bin -cp bin src/de/meinefirma/main/*.java

java -cp bin de.meinefirma.main.XmlParserMitStaxUndJaxb

Im ersten Testlauf schalten wir die Validierung aus, indem in der Kommandozeile als erster Parameter "" gesetzt wird:

java -cp bin de.meinefirma.main.XmlParserMitStaxUndJaxb "" Buecher.xml de.meinefirma.buecherentities

Sie erhalten:

Schema: Buecher.xsd, XML-Dokument: Buecher.xml, XML-RootElement: buecher

de.meinefirma.buecherentities.AutorType
de.meinefirma.buecherentities.AutorType
de.meinefirma.buecherentities.VerlagType
de.meinefirma.buecherentities.VerlagType
de.meinefirma.buecherentities.BuchType

Elementeanzahl: 5, Parsingdauer: 80 ms

Jetzt aktivieren wir die Validierung, indem als erster Parameter die Schema-XSD-Datei Buecher.xsd angegeben wird, und erhalten eine etwas längere Parsingdauer:

java -cp bin de.meinefirma.main.XmlParserMitStaxUndJaxb Buecher.xsd Buecher.xml de.meinefirma.buecherentities

Schema: Buecher.xsd, XML-Dokument: Buecher.xml, XML-RootElement: buecher

de.meinefirma.buecherentities.AutorType
de.meinefirma.buecherentities.AutorType
de.meinefirma.buecherentities.VerlagType
de.meinefirma.buecherentities.VerlagType
de.meinefirma.buecherentities.BuchType

Elementeanzahl: 5, Parsingdauer: 110 ms

Wenn Sie commons-beanutils-1.8.2.jar und commons-logging-1.1.1.jar in ein lib-Unterverzeichnis kopieren, dem Classpath hinzufügen und in der DefaultElementeVerarbeitungImpl-Klasse die Zeile "return new TreeMap( PropertyUtils.describe( element ) ); einkommentieren, wird auch der Inhalt der Elemente angezeigt:

java -cp bin;lib/* de.meinefirma.main.XmlParserMitStaxUndJaxb Buecher.xsd Buecher.xml de.meinefirma.buecherentities

Schema: Buecher.xsd, XML-Dokument: Buecher.xml, XML-RootElement: buecher

{class=class de.meinefirma.buecherentities.AutorType,  id=42,  name=Hinz, ort=Hamburg}
{class=class de.meinefirma.buecherentities.AutorType,  id=43,  name=Kunz, ort=Krefeld}
{class=class de.meinefirma.buecherentities.VerlagType, id=151, name=Aachener Java-Verlag, ort=Aachen}
{class=class de.meinefirma.buecherentities.VerlagType, id=152, name=Bonner XML-Verlag, ort=Bonn}
{class=class de.meinefirma.buecherentities.BuchType,   autorID=43, verlagID=151, titel=XML mit Java}

Elementeanzahl: 5, Parsingdauer: 150 ms

Solange das XML-Dokument klein ist und nur 5 Elemente enthält, spielt der Speicherverbrauch keine Rolle. Erst bei großen XML-Dokumenten mit vielen Elementen wird der Vorteil sichtbar. Um dies messen zu können, wurden vier verschieden große dem obigen XSD-Schema entsprechende XML-Dokumente erstellt und die Parsingdauer und der Speicherverbrauch gemessen (der JVM wurden dabei per -Xmx120M max. 120 MByte Speicher zugewiesen):


Größe
XML-Datei
Anzahl
XML-Elemente
JAXB mit StAX
XmlParserMitStaxUndJaxb
JAXB ohne StAX
JaxbMarshalUnmarshalUtil
ohne Validierungmit Validierungohne Validierungmit ValidierungSpeicherverbrauch
1 KByte100,01 s0,01 s0,01 s0,01 s0 MByte
8 MByte100.0001,1 s1,7 s0,5 s1,4 s10 MByte
76 MByte1.000.00011 s17 s5 s14 s100 MByte
760 MByte10.000.000110 s170 s-- -- -- OutOfMemoryError -- -- --

Wenn man sich auf die ersten 12 Messungen mit den ersten drei XML-Dateien beschränkt (bei denen es keinen OutOfMemoryError gibt) und alle Messungen nacheinander innerhalb einer JVM ausführt, kann man den Speicherverbrauch schön mit JConsole oder mit JVisualVM verfolgen:

JAXB-mit-StAX-JConsole.png

Ungefähr bis zum senkrechten Strich bei 20:43 Uhr finden die Parsings der "JAXB mit StAX"-Version statt: Dabei beträgt der Speicherverbrauch nur wenige MByte. Anschließend folgen die Parsings der "JAXB ohne StAX"-Version: Man sieht deutlich die beiden Anstiege auf über 100 MByte Speicherverbrauch.

Java VisualVM visualisiert zusätzlich die massiven Aktivitäten der Garbage Collection ("GC activity") bei der "JAXB ohne StAX"-Version:

JAXB-mit-StAX-JVisualVM.png

Falls Sie den Profiler von JVisualVM verwenden wollen: Wählen Sie den Tabulatorreiter "Profiler", aktivieren Sie "Settings", entfernen Sie unten rechts im "Do not profile classes"-Fenster den "javax.*,"-Eintrag und betätigen Sie "Profile: CPU". Als Ergebnis erhalten Sie, dass javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal() und javax.xml.validation.Validator.validate() viel Zeit benötigen.

Um die Messungen nachzuvollziehen, sind die im Folgenden beschriebenen Schritte notwendig:

Speichern Sie im src/de/meinefirma/main-Verzeichnis obige JaxbMarshalUnmarshalUtil.java und folgende VergleichJaxbVersusJaxbMitStax.java:

package de.meinefirma.main;

import java.util.Arrays;

public class VergleichJaxbVersusJaxbMitStax
{
   public static void main( String[] args ) throws Exception
   {
      if( args.length < 3 ) {
         System.out.println( "\nBitte Klasse, XSD-Schema und XML-Dokument(e) angeben, z.B.:\n" +
                             "java de.meinefirma.main.VergleichJaxbVersusJaxbMitStax" +
                             " de.meinefirma.buecherentities.Buecher BuecherOhneKeyref.xsd Buecher10.xml Buecher100.xml" );
         return;
      }
      vergleiche( args[0], args[1], Arrays.copyOfRange( args, 2, args.length ) );
   }

   public static void vergleiche( String clazz, String xsdSchema, String[] xmlDateien ) throws Exception
   {
      Class<?> clss        = Class.forName( clazz );
      String   packageName = clss.getPackage().getName();
      System.out.println( "\nSchema: " + xsdSchema + ", Klasse: " + clazz );
      Thread.sleep( 3000 );

      // JIT vorbereiten:
      XmlParserMitStaxUndJaxb.parseXmlElemente(
            xsdSchema, xmlDateien[0], packageName, new DefaultElementeVerarbeitungImpl(), false );
      XmlParserMitStaxUndJaxb.parseXmlElemente(
            xsdSchema, xmlDateien[0], packageName, new DefaultElementeVerarbeitungImpl(), false );

      // JAXB mit StAX, ohne Validierung:
      System.out.println( "\nJAXB mit StAX, ohne Validierung:" );
      for( String xmlDatei : xmlDateien ) {
         testeJaxbMitStax( null, xmlDatei, packageName );
      }
      // JAXB mit StAX, mit Validierung:
      System.out.println( "\nJAXB mit StAX, mit Validierung:" );
      for( String xmlDatei : xmlDateien ) {
         testeJaxbMitStax( xsdSchema, xmlDatei, packageName );
      }

      // JIT vorbereiten:
      JaxbMarshalUnmarshalUtil.unmarshal( xsdSchema, xmlDateien[0], clss );
      JaxbMarshalUnmarshalUtil.unmarshal( xsdSchema, xmlDateien[0], clss );

      // JAXB ohne StAX, ohne Validierung:
      System.out.println( "\nJAXB ohne StAX, ohne Validierung:" );
      for( String xmlDatei : xmlDateien ) {
         testeNurJaxb( null, xmlDatei, clss );
      }
      // JAXB ohne StAX, mit Validierung:
      System.out.println( "\nJAXB ohne StAX, mit Validierung:" );
      for( String xmlDatei : xmlDateien ) {
         testeNurJaxb( xsdSchema, xmlDatei, clss );
      }

      System.gc();
   }

   static void testeJaxbMitStax( String xsdSchema, String xmlDatei, String packageName ) throws Exception
   {
      System.out.println( "XML-Datei = " + xmlDatei + ":" );
      long startZeit = System.nanoTime();
      long anzahlElemente = XmlParserMitStaxUndJaxb.parseXmlElemente(
            xsdSchema, xmlDatei, packageName, new DefaultElementeVerarbeitungImpl(), false );
      String dauer = JaxbMarshalUnmarshalUtil.ermittleDauer( startZeit );
      System.out.println( "Elementeanzahl = " + anzahlElemente + ", Parsingdauer = " + dauer );
   }

   static void testeNurJaxb( String xsdSchema, String xmlDatei, Class<?> clss ) throws Exception
   {
      System.out.println( "XML-Datei = " + xmlDatei + ":" );
      long startSpeicherverbrauch = JaxbMarshalUnmarshalUtil.ermittleSpeicherverbrauch();
      long startZeit = System.nanoTime();
      Object obj = JaxbMarshalUnmarshalUtil.unmarshal( xsdSchema, xmlDatei, clss );
      String dauer = JaxbMarshalUnmarshalUtil.ermittleDauer( startZeit );
      String speicherverbrauch = JaxbMarshalUnmarshalUtil.formatiereSpeichergroesse(
            JaxbMarshalUnmarshalUtil.ermittleSpeicherverbrauch() - startSpeicherverbrauch );
      if( !(obj.getClass().equals(clss)) ) throw new Exception( "Falsche Klasse." );
      System.out.println( "Parsingspeicherverbrauch = " + speicherverbrauch + ", Parsingdauer = " + dauer );
   }
}

Eine wichtige Voraussetzung ist, dass Sie die Buecher.xsd in die neue Datei BuecherOhneKeyref.xsd kopieren und in der neuen Schemadatei die beiden "<xsd:key ...>"- und die beiden "<xsd:keyref ...>"-Abschnitte entfernen. Ansonsten würden Sie bei der Validierung einen OutOfMemoryError erhalten, weil die Validierung zur Überprüfung auf gültige Keys die XML-Datei komplett in den Speicher laden müsste.

Erzeugen Sie die zu verwendenden großen XML-Dateien Buecher10.xml, Buecher100000.xml und Buecher1000000.xml (z.B. mit BuecherXmlErzeuger.java aus JAXB.zip) und führen Sie folgende Kommandos aus:

javac -d bin -cp bin src/de/meinefirma/main/*.java

start jconsole -interval=1 localhost:4711

start jvisualvm --openjmx localhost:4711

java -Xmx120M -Dcom.sun.management.jmxremote.port=4711 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -cp bin de.meinefirma.main.VergleichJaxbVersusJaxbMitStax de.meinefirma.buecherentities.Buecher BuecherOhneKeyref.xsd Buecher10.xml Buecher100000.xml Buecher1000000.xml

Die letzten Kommandos müssen kurz hintereinander erfolgen (z.B. in einer Batchdatei).





Weitere Themen: andere TechDocs | XML | XSD (Schema) | XSL, XSLT, XSL-FO
© 2009-2010 Torsten Horn, Aachen