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
  8. Erzeugen 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:
http://jaxb.java.net
http://docs.oracle.com/javase/7/docs/api/javax/xml/bind/package-summary.html
http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/2.0/tutorial/doc/JAXBWorks.html
http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/2.0/tutorial/doc/JAXBUsing.html
Vergleich von JAXB mit Apache XMLBeans, ADB und JiBX
Phillip Ghadir zu XML und JAXB



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 (z.B. im Verzeichnis \MeinWorkspace\JAXB) folgendes XML-Dokument: Buecher.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<buecher xmlns="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>

</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"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns="http://meinnamespace.meinefirma.de"
           targetNamespace="http://meinnamespace.meinefirma.de"
           elementFormDefault="qualified">

   <xs:element name="buecher">
      <xs:complexType>
         <xs:sequence>
            <xs:element name="autor"  type="AutorType"  maxOccurs="unbounded" />
            <xs:element name="verlag" type="VerlagType" maxOccurs="unbounded" />
            <xs:element name="buch"   type="BuchType"   maxOccurs="unbounded" />
         </xs:sequence>
      </xs:complexType>
      <xs:key         name="autorKey">
         <xs:selector xpath="autor" />
         <xs:field    xpath="@id" />
      </xs:key>
      <xs:keyref      name="autorKeyref" refer="autorKey">
         <xs:selector xpath="buch" />
         <xs:field    xpath="@autorID" />
      </xs:keyref>
      <xs:key         name="verlagKey">
         <xs:selector xpath="verlag" />
         <xs:field    xpath="@id" />
      </xs:key>
      <xs:keyref      name="verlagKeyref" refer="verlagKey">
         <xs:selector xpath="buch" />
         <xs:field    xpath="@verlagID" />
      </xs:keyref>
   </xs:element>

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

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

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

</xs: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(required = true)
    protected String name;
    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 7 bei der Standard-Java/JDK-Installation üblich ist. Obwohl Windows 7 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(required = true)
   protected String name;
   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:

cd \MeinWorkspace\JAXB

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"?>
<buecher xmlns="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>
</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 sollen wieder die obige Schema-XSD-Datei Buecher.xsd und die obige Beispiel-XML-Datei Buecher.xml verwendet werden. Um die dazu passenden Java-Klassen zu generieren, führen Sie wieder im Kommandozeilenfenster aus:

cd \MeinWorkspace\JAXB

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( name = "autor" ) in AutorType.java
@javax.xml.bind.annotation.XmlRootElement( name = "buch" ) in BuchType.java
@javax.xml.bind.annotation.XmlRootElement( 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. Sehen Sie sich zu diesem Thema auch an: JAXB: UnmarshalException ... und Why does JAXB put @XmlRootElement sometimes but not always?. Falls Sie die Exception
javax.xml.bind.UnmarshalException: unexpected element ... Expected elements are ...
erhalten: Überprüfen Sie, dass Sie bei der Annotation @XmlRootElement( name="mein-xml-element" ) einen Namen angegeben haben, und dass dieser korrekt ist.

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

package de.meinefirma.main;

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

/**
 * Hilfsklasse zum Parsen grosser XML-Dateien (durch Kombination von JAXB mit StAX).<br>
 * Es wird nicht das StartElement geparst, aber alle darunter angeordneten Elemente.
 */
public class XmlParserMitStaxUndJaxb
{
   /**
    * Interface fuer Callback-Klassen fuer die Verarbeitung der einzelnen XML-Elemente.
    */
   public interface ElementeVerarbeitung
   {
      void verarbeite( Object element );
   }

   /**
    * Hilfsmethode zum Parsen grosser XML-Dateien (durch Kombination von JAXB mit StAX).<br>
    * Es koennen alle im angegebenen Package vorhandenen Klassen geparst werden, welche eine XmlRootElement-Annotation haben.<br>
    *
    * @param  xsdDatei    Schema-XSD-Datei
    * @param  xmlDatei    XML-Datei
    * @param  encoding    Character-Encoding, z.B. UTF-8
    * @param  packageName Package mit den zu lesenden Java-Klassen
    * @param  elemVerarb  Callback-Objekt fuer die Verarbeitung der einzelnen Elemente
    * @return Anzahl der gefundenen Elemente
    */
   public static long parseXmlElemente( String xsdDatei, String xmlDatei, String encoding, String packageName, ElementeVerarbeitung elemVerarb ) throws Exception
   {
      if( xsdDatei != null && xsdDatei.trim().length() > 0 ) {
         try( Reader xml = new InputStreamReader( new FileInputStream( xmlDatei ), encoding ) ) {
            validate( xsdDatei, xml );
         }
      }
      try( Reader xml = new InputStreamReader( new FileInputStream( xmlDatei ), encoding ) ) {
         return parseXmlElemente( xml, packageName, elemVerarb );
      }
   }

   /**
    * Hilfsmethode zum Parsen grosser XML-Dateien (durch Kombination von JAXB mit StAX).<br>
    * Es koennen alle im angegebenen Package vorhandenen Klassen geparst werden, welche eine XmlRootElement-Annotation haben.<br>
    * <b>Achtung:</b> Zum XML-Reader wird normalerweise kein close() aufgerufen, ausser siehe Bug:
    * {@link "http://bugs.sun.com/view_bug.do?bug_id=6539065"}
    *
    * @param  xml         Reader zur XML-Datei
    * @param  packageName Package mit den zu lesenden Java-Klassen
    * @param  elemVerarb  Callback-Objekt fuer die Verarbeitung der einzelnen Elemente
    * @return Anzahl der gefundenen Elemente
    */
   public static long parseXmlElemente( Reader xml, String packageName, ElementeVerarbeitung elemVerarb ) throws XMLStreamException, JAXBException
   {
      // 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( xml );
      XMLEventReader  staxFiltRd   = staxFactory.createFilteredReader( staxReader, startElementFilter );
      // Ueberspringe StartElement:
      staxFiltRd.nextEvent();
      // JAXB:
      JAXBContext     jaxbContext  = JAXBContext.newInstance( packageName );
      Unmarshaller    unmarshaller = jaxbContext.createUnmarshaller();
      // Parsing:
      while( staxFiltRd.peek() != null ) {
         Object element = unmarshaller.unmarshal( staxReader );
         elemVerarb.verarbeite( element );
         anzahlElem++;
      }
      return anzahlElem;
   }

   /**
    * Hilfsmethode zum Parsen grosser XML-Dateien (durch Kombination von JAXB mit StAX).<br>
    * Es kann nur die eine angegebene Klasse geparst werden (eine XmlRootElement-Annotation wird nicht benoetigt).<br>
    * <b>Achtung:</b> Zum XML-Reader wird normalerweise kein close() aufgerufen, ausser siehe Bug:
    * {@link "http://bugs.sun.com/view_bug.do?bug_id=6539065"}
    *
    * @param  xml          Reader zur XML-Datei
    * @param  elementClass Die zu parsende Java-Klasse
    * @param  elemVerarb   Callback-Objekt fuer die Verarbeitung der einzelnen Elemente
    * @return Anzahl der gefundenen Elemente
    */
   public static long parseXmlElemente( Reader xml, Class<?> elementClass, ElementeVerarbeitung elemVerarb ) throws XMLStreamException, JAXBException
   {
      // 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( xml );
      XMLEventReader  staxFiltRd   = staxFactory.createFilteredReader( staxReader, startElementFilter );
      // Ueberspringe StartElement:
      staxFiltRd.nextEvent();
      // JAXB:
      JAXBContext     jaxbContext  = JAXBContext.newInstance( elementClass );
      Unmarshaller    unmarshaller = jaxbContext.createUnmarshaller();
      // Parsing:
      while( staxFiltRd.peek() != null ) {
         Object element = unmarshaller.unmarshal( staxReader, elementClass );
         if( element instanceof JAXBElement && ((JAXBElement<?>) element).getValue() != null ) {
            element = ((JAXBElement<?>) element).getValue();
         }
         elemVerarb.verarbeite( element );
         anzahlElem++;
      }
      return anzahlElem;
   }

   /**
    * Validierung des XML-Readers gegen ein XSD-Schema.<br>
    * <b>Achtung:</b> Zum XML-Reader wird kein close() aufgerufen.
    *
    * @param xsdSchema XSD-Schema
    * @param xml       XML-Reader
    */
   public static void validate( String xsdSchema, Reader xml ) throws SAXException, IOException
   {
      // Der Ausdruck "javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI" kann Probleme machen, deshalb: 
      final String  w3cXmlSchemaNsUri = "http://www.w3.org/2001/XMLSchema";
      SchemaFactory schemaFactory = SchemaFactory.newInstance( w3cXmlSchemaNsUri );
      Schema        schema = schemaFactory.newSchema( new File( xsdSchema ) );
      Validator     validator = schema.newValidator();
      validator.validate( new StreamSource( xml ) );
   }
}

Und speichern Sie im src/de/meinefirma/main-Verzeichnis folgende JUnit-Testklasse: XmlParserMitStaxUndJaxbTest.java

package de.meinefirma.main;

import java.io.*;
import java.text.DecimalFormat;
import java.util.*;
import org.junit.*;
import de.meinefirma.buecherentities.*;

public class XmlParserMitStaxUndJaxbTest
{
   private static final String TEST_XSD_DATEI = "Buecher.xsd";
   private static final String TEST_XML_DATEI = "Buecher.xml";
   private static final String ENCODING       = "UTF-8";
   private static final String PACKAGE        = "de.meinefirma.buecherentities";

   /** Die main()-Methode ist nur fuer manuelle Testzwecke */
   public static void main( String[] args ) throws Exception
   {
      if( args.length != 4 ) {
         System.out.println( "\nBitte XSD-Schema, XML-Dokument, Encoding und den Package-Namen der Zielklassen angeben, z.B.:\n" +
                             "java XmlParserMitStaxUndJaxbTest Buecher.xsd Buecher.xml UTF-8 de.meinefirma.buecherentities\n" );
         return;
      }

      if( args[0] != null && args[0].trim().length() > 0 ) {
         long startZeit = System.nanoTime();
         try( Reader xml = new InputStreamReader( new FileInputStream( args[1] ), args[2] ) ) {
            XmlParserMitStaxUndJaxb.validate( args[0], xml );
         }
         System.out.println( "\nValidierungsdauer: " + ermittleDauer( startZeit ) + "\n" );
      }

      long anzahlElemente = 0;
      long startZeit = System.nanoTime();
      try( Reader xml = new InputStreamReader( new FileInputStream( args[1] ), args[2] ) ) {
         anzahlElemente = XmlParserMitStaxUndJaxb.parseXmlElemente( xml, args[3], new DefaultElementeVerarbeitungImpl() );
      }
      System.out.println( "\nElementeanzahl: " + anzahlElemente + ", Parsingdauer: " + ermittleDauer( startZeit ) + "\n" );
   }

   @Test
   public void testValidatorUndParser() throws Exception
   {
      ElementeSpeicherungInListe elementeSpeicherung = new ElementeSpeicherungInListe();
      long anzahlElemente = XmlParserMitStaxUndJaxb.parseXmlElemente( TEST_XSD_DATEI, TEST_XML_DATEI, ENCODING, PACKAGE, elementeSpeicherung );
      Assert.assertEquals( 5, anzahlElemente );
      Assert.assertEquals( 5, elementeSpeicherung.elemente.size() );
      Assert.assertTrue( elementeSpeicherung.elemente.get( 0 ) instanceof AutorType );
      AutorType autor = (AutorType) elementeSpeicherung.elemente.get( 0 );
      Assert.assertEquals(    "Hinz", autor.getName() );
      Assert.assertEquals( "Hamburg", autor.getOrt() );
      Assert.assertTrue( elementeSpeicherung.elemente.get( 4 ) instanceof BuchType );
      BuchType buch = (BuchType) elementeSpeicherung.elemente.get( 4 );
      Assert.assertEquals(             43, buch.getAutorID() );
      Assert.assertEquals(            151, buch.getVerlagID() );
      Assert.assertEquals( "XML mit Java", buch.getTitel() );
   }

   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";
   }

   /**
    * ElementeVerarbeitung-Callback-Klasse, welche die XML-Elemente lediglich in einer Liste speichert.<br>
    * (Darf nur bei nicht zu grossen XML-Dateien verwendet werden, um OutOfMemory zu vermeiden.)
    */
   static class ElementeSpeicherungInListe implements XmlParserMitStaxUndJaxb.ElementeVerarbeitung
   {
      public List<Object> elemente = new ArrayList<Object>();

      @Override
      public void verarbeite( Object element )
      {
         this.elemente.add( element );
      }
   }

   static class DefaultElementeVerarbeitungImpl implements XmlParserMitStaxUndJaxb.ElementeVerarbeitung
   {
      @Override public void verarbeite( Object element )
      {
         try {
            // 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:
            System.out.println( element.getClass().getName() );
         } catch( Exception ex ) {
            throw new RuntimeException( ex );
         }
      }
   }
}

Das XML-Dokument Buecher.xml 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 "@XmlRootElement( name = "..." )" hinzufügen müssten.

Für den JUnit-Test kopieren Sie junit-4.12.jar und hamcrest-core-1.3.jar in ein lib-Unterverzeichnis zum Projektverzeichnis.
Ihr Projektverzeichnis sieht jetzt so aus (überprüfen Sie es mit tree /F):

[\MeinWorkspace\JAXB]
 |- [lib]
 |   |- hamcrest-core-1.3.jar
 |   '- junit-4.12.jar
 |- [src]
 |   '- [de]
 |       '- [meinefirma]
 |           |- [buecherentities]
 |           |   |- AutorType.java
 |           |   |- BuchType.java
 |           |   |- Buecher.java
 |           |   |- ObjectFactory.java
 |           |   |- package-info.java
 |           |   '- VerlagType.java
 |           '- [main]
 |               |- XmlParserMitStaxUndJaxb.java
 |               '- XmlParserMitStaxUndJaxbTest.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;lib/* src/de/meinefirma/main/*.java

java -cp bin;lib/* org.junit.runner.JUnitCore de.meinefirma.main.XmlParserMitStaxUndJaxbTest

java -cp bin;lib/* de.meinefirma.main.XmlParserMitStaxUndJaxbTest

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

java -cp bin;lib/* de.meinefirma.main.XmlParserMitStaxUndJaxbTest "" Buecher.xml UTF-8 de.meinefirma.buecherentities

Sie erhalten:

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:

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

Validierungsdauer: 50 ms

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

Elementeanzahl: 5, Parsingdauer: 65 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/*;lib/* de.meinefirma.main.XmlParserMitStaxUndJaxbTest Buecher.xsd Buecher.xml UTF-8 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
{
   static final String ENCODING = "UTF-8";

   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], ENCODING, packageName, new XmlParserMitStaxUndJaxbTest.DefaultElementeVerarbeitungImpl() );
      XmlParserMitStaxUndJaxb.parseXmlElemente(
            xsdSchema, xmlDateien[0], ENCODING, packageName, new XmlParserMitStaxUndJaxbTest.DefaultElementeVerarbeitungImpl() );

      // 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, ENCODING, packageName, new XmlParserMitStaxUndJaxbTest.DefaultElementeVerarbeitungImpl() );
      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 "<xs:key ...>"- und die beiden "<xs: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;lib/* 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;lib/* 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).



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

Ähnlich wie beim Parsen (siehe oben: Parsen großer XML-Dokumente durch Kombination von JAXB mit StAX) können auch beim Erzeugen großer XML-Dokumente JAXB und StAX kombiniert werden, um zu vermeiden, dass das ganze Dokument in den Speicher geladen werden muss, was möglicherweise zu Speicherengpässen führen könnte.

Das folgende Beispiel setzt auf dem vorherigen auf.

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

package de.meinefirma.main;

import java.io.*;
import java.util.Iterator;
import javax.xml.bind.*;
import javax.xml.stream.*;
import de.meinefirma.buecherentities.*;

public class XmlErzeugerMitStaxUndJaxb
{
   public static void main( String[] args ) throws Exception
   {
      String outFile = ( args.length > 0 ) ? args[0] : null;
      String charEnc = ( args.length > 1 ) ? args[1] : "UTF-8";
      erzeugeXML( outFile, charEnc );
      if( outFile != null ) {
         try( Reader rd = new InputStreamReader( new FileInputStream( outFile ) ) ) {
            XmlParserMitStaxUndJaxb.validate( "Buecher.xsd", rd );
         }
      }
   }

   public static void erzeugeXML( String outXmlFile, String xmlCharEnc ) throws Exception
   {
      try( OutputStream os = ( outXmlFile == null ) ? System.out : new FileOutputStream( new File( outXmlFile ) );
           Writer wr = new OutputStreamWriter( os, xmlCharEnc ) ) {
         XMLStreamWriter xsw = XMLOutputFactory.newInstance().createXMLStreamWriter( wr );
         // xsw = new IndentingXMLStreamWriter( xsw );
         try {
            Marshaller marshaller = JAXBContext.newInstance( Buecher.class ).createMarshaller();
            marshaller.setProperty( Marshaller.JAXB_ENCODING, xmlCharEnc );
            marshaller.setProperty( Marshaller.JAXB_FRAGMENT, Boolean.TRUE );

            xsw.writeStartDocument( xmlCharEnc, null );
            xsw.writeCharacters( "\r\n" );
            xsw.writeStartElement( "buecher" );
            xsw.setDefaultNamespace(   "http://meinnamespace.meinefirma.de" );
            xsw.writeDefaultNamespace( "http://meinnamespace.meinefirma.de" );
            xsw.writeNamespace( "xsi", "http://www.w3.org/2001/XMLSchema-instance" );
            xsw.writeAttribute( "xsi:schemaLocation", "http://meinnamespace.meinefirma.de Buecher.xsd" );
            xsw.writeCharacters( "\r\n" );

            AutorenDatenLieferant a = new AutorenDatenLieferant();
            VerlageDatenLieferant v = new VerlageDatenLieferant();
            BuecherDatenLieferant b = new BuecherDatenLieferant();
            while( a.hasNext() ) { marshaller.marshal( a.next(), xsw ); xsw.writeCharacters( "\r\n" ); }
            while( v.hasNext() ) { marshaller.marshal( v.next(), xsw ); xsw.writeCharacters( "\r\n" ); }
            while( b.hasNext() ) { marshaller.marshal( b.next(), xsw ); xsw.writeCharacters( "\r\n" ); }

            xsw.writeEndElement();
            xsw.writeEndDocument();
            xsw.writeCharacters( "\r\n" );
            xsw.flush();
         } finally {
            xsw.close();
         }
      }
   }

   static class AutorenDatenLieferant implements Iterator<AutorType> {
      private int idx = 0;
      @Override public void remove() { throw new UnsupportedOperationException(); }
      @Override public boolean hasNext() { return idx < 1; }
      @Override public AutorType next() {
         AutorType a = new AutorType();
         a.setId( 40 + ++idx );
         a.setName( "Otto" );
         a.setOrt( "Hamburg" );
         return a;
      }
   }

   static class VerlageDatenLieferant implements Iterator<VerlagType> {
      private int idx = 0;
      @Override public void remove() { throw new UnsupportedOperationException(); }
      @Override public boolean hasNext() { return idx < 1; }
      @Override public VerlagType next() {
         VerlagType v = new VerlagType();
         v.setId( 4000 + ++idx );
         v.setName( "Elefanten-Verlag" );
         v.setOrt( "Berlin" );
         return v;
      }
   }

   static class BuecherDatenLieferant implements Iterator<BuchType> {
      private int idx = 0;
      @Override public void remove() { throw new UnsupportedOperationException(); }
      @Override public boolean hasNext() { return idx < 2; }
      @Override public BuchType next() {
         BuchType b = new BuchType();
         b.setAutorID( 41 );
         b.setVerlagID( 4001 );
         b.setTitel( "Meine Elefanten " + ++idx );
         return b;
      }
   }
}

Die drei Iterator-Klassen AutorenDatenLieferant, VerlageDatenLieferant und BuecherDatenLieferant müssen durch sinnvollere Klassen ersetzt werden, welche die jeweiligen Daten liefern (ohne alles gleichzeitig in den Hauptspeicher zu laden). Zum Beispiel könnten Sie die Daten zeilenweise aus einer Datenbank lesen.

Führen Sie im Kommandozeilenfenster aus:

cd \MeinWorkspace\JAXB

md bin

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

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

java -cp bin;lib/* de.meinefirma.main.XmlErzeugerMitStaxUndJaxb

--> Das erzeugte XML wird im Kommandozeilenfenster ausgegeben.

java -cp bin;lib/* de.meinefirma.main.XmlErzeugerMitStaxUndJaxb Buecher-erzeugt.xml

type Buecher-erzeugt.xml

--> Das erzeugte XML wird in die Datei Buecher-erzeugt.xml gespeichert (und gegen Buecher.xsd validiert).

java -cp bin;lib/* de.meinefirma.main.XmlErzeugerMitStaxUndJaxb Buecher-erzeugt-ISO-8859-1.xml ISO-8859-1

type Buecher-erzeugt-ISO-8859-1.xml

--> Die XML-Datei Buecher-erzeugt-ISO-8859-1.xml wird mit ISO-8859-1-Encoding gespeichert.
Testen Sie das Character-Encoding, indem Sie Umlaute und das Eurozeichen einfügen.

Wenn Sie eine schönere Formatierung des XMLs wünschen (mit "Indenting" und "Linefeeds"), dann entfernen Sie die Auskommentierung vor "xsw = new IndentingXMLStreamWriter( xsw );" und sorgen Sie dafür, dass eine geeignete IndentingXMLStreamWriter-Klasse zur Verfügung steht. Hierzu gibt es zwei Möglichkeiten:

Entweder Sie verwenden "com.sun.xml.internal.txw2.output.IndentingXMLStreamWriter" zum Beispiel aus rt.jar, aber dann kann es Probleme in unterschiedlichen Laufzeitumgebungen kommen (z.B. Maven-Build oder Java EE Application Server).

Oder Sie fügen die IndentingXMLStreamWriter.java-Datei (und die ebenfalls benötigte DelegatingXMLStreamWriter.java) Ihrem Sourcecodeverzeichnis hinzu. Sie finden den Sourcecode, wenn Sie im Internet nach "IndentingXMLStreamWriter DelegatingXMLStreamWriter Kohsuke Kawaguchi" suchen (beachten Sie die Lizenzbedingungen).

Natürlich sollten Sie dann alle "xsw.writeCharacters( "\r\n" );" aus obigem Beipiel entfernen.

Falls Sie in den Klassen der per JAXB zu speichernden Elemente (im Beispiel AutorType, VerlagType und BuchType) nicht die @XmlRootElement-Annotationen hinzufügen wollen (oder können), können Sie alternativ die drei Iteratoren so umbauen, dass Sie JAXBElement returnieren, zum Beispiel für den AutorenDatenLieferant folgendermaßen:

   static class AutorenDatenLieferant implements Iterator<JAXBElement<AutorType>> {
      private int idx = 0;
      @Override public void remove() { throw new UnsupportedOperationException(); }
      @Override public boolean hasNext() { return idx < 1; }
      @Override public JAXBElement<AutorType> next() {
         AutorType a = new AutorType();
         a.setId( 40 + ++idx );
         a.setName( "Otto" );
         a.setOrt( "Hamburg" );
         return new JAXBElement<AutorType>( new QName( "http://meinnamespace.meinefirma.de", "autor" ), AutorType.class, a );
      }
   }

Falls Sie Namespace-Probleme haben, sehen Sie sich Folgendes an:

XMLStreamWriter

Using XMLStreamWriter





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