XML als Datenaustauschformat / JAXB

JAXB

JAXB

➪ Alternativ zur Arbeit mit java.beans.XMLEncoder und java.beans.XMLDecoder gibt es die Möglichkeit, Java-Objekte mit JAXB in eine XML-Datei zu serialisieren und sie analog wieder zu deserialisieren. Ein Vorteil liegt darin, über Annotations auf die Struktur des XML-Dokuments Einfluss zu nehmen.

Auf dieser Seite:

Zur Demonstration erweitere ich die Klasse Mensch um sogenannte Annotations. Unter anderem sorgt @XmlRootElement für die Bezeichnung des Root-Elements und Namespaces, @XmlType(propOrder) für die Reihenfolge der Elemente bzw. Attribute im XML-Ergebnisdokument. @XmlAttribute stellt die Information in Attributschreibweise dar.


import javax.xml.bind.annotation.*;
@XmlRootElement(name = "MENSCH", 
     namespace = "wilfried-grupe.de/jaxb")
@XmlType( propOrder = 
     {"nachname", "NN", "vorname", "VN", "hobby", "alter", "a"})
public class Mensch implements IXMLTest, Serializable {
  private String vorname, nachname;  
  private String _hobby;
  private int alter;
  //Getters, Setters
  //Constructors
  //Implemented methods
  @XmlElement(nillable=true, required=false)
  public String getNachname() {
    return nachname;
  }
  @XmlAttribute(namespace="wilfried-grupe.de/jaxb/demo", 
  required=true, name = "HOBBY")
  public String getHobby() {
    return this._hobby;
  }
  @XmlAttribute(
      namespace="wilfried-grupe.de/jaxb/demo", name = "ALTER1")
  public int getAlter() {
    return alter;
  }
  @XmlAttribute(
      namespace="wilfried-grupe.de/jaxb/demo", name = "ALTER2")
  public int getA() {
    return this.getAlter();
  }
}

Nun generiere ich ein Objekt der Klasse mit JAXB und marshalle es in eine Datei.


  Mensch m = new Mensch("Wilma", "Wolkenlos", 33, "XML");
  try {
    JAXBContext context;
    context = JAXBContext.newInstance(Mensch.class);
    Marshaller mm = context.createMarshaller();
    mm.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    mm.marshal(m, new File("C:/wg/jaxb_demo.xml"));
  } catch (PropertyException e) { } 
    catch (JAXBException e) { }

Im Ergebnis finden Sie ein XML-Dokument mit mehreren Namespaces, Attributen und Elementen.


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:MENSCH 
  xmlns:ns2="wilfried-grupe.de/jaxb/demo" 
  xmlns:ns3="wilfried-grupe.de/jaxb" 
  ns2:HOBBY="XML" 
  ns2:ALTER1="33" 
  ns2:ALTER2="33">
    <nachname>Wolkenlos</nachname>
    <NN>Wolkenlos</NN>
    <vorname>Wilma</vorname>
    <VN>Wilma</VN>
</ns3:MENSCH>

Anmerkungen zu den Annotations (z.B. @XmlElement(nillable=false, required=false)):

Umgekehrt kann dieses XML-Dokument unmarshalled und in ein Objekt der Klasse Mensch (dann ist der Zugriff auf m.getHobby() möglich) oder in ein Schnittstellenobjekt IXMLTest überführt werden.


  try {
    JAXBContext context;
    context = JAXBContext.newInstance(Mensch.class);
    Unmarshaller um = context.createUnmarshaller();
    IXMLTest m = (IXMLTest) um.unmarshal(new FileReader(
      "C:/wg/jaxb_demo.xml"));
    //System.out.println(m.getHobby());
    System.out.println(m.getVN());
    System.out.println(m.getNN());
    System.out.println(m.getA());
  } catch (FileNotFoundException e) {  } 
    catch (JAXBException e) {  }

Ein Casten des generierten Mensch-Objekts auf Person läuft jedoch auf einen Fehler:


Person m = (Person) um.unmarshal(new FileReader(...));
Exception in thread "main" java.lang.ClassCastException: 
Mensch cannot be cast to Person

Ebenso erhalten Sie einen Fehler, wenn der JAXBContext auf die Klasse Person ausgerichtet, der XML-Stream aber unverändert ist:


JAXBContext context = JAXBContext.newInstance(Person.class);
Person m = (Person) um.unmarshal(new FileReader(...));
unerwartetes Element (URI:"wilfried-grupe.de/jaxb", lokal:"MENSCH"). 
Erwartete Elemente sind (none)

JAXB und XML-Schema: xjc.exe

JAXB lässt sich aber noch in anderer Form verwenden, nämlich ohne eine vordefinierte Klasse Mensch, nur via XML-Schema und XML.


xjc.exe <SCHEMADATEI>

generiert aus einem XML-Schema Dokument automatisch zwei Java-Dateien, von denen die eine ObjectFactory.java heißt, die andere (abhängig vom XML-Schema) z.B. Mensch.java oder Orte.java. Mit diesen Klassen ist es möglich, die Informationen im XML-Dokument sofort in Java-Objekte zu übernehmen. Darüber hinaus können Sie dem Unmarshaller noch das XML-Schema-Dokument zuweisen, dadurch wird das XML-Input-Dokument gleich validiert.


 public static void JaxBinding() {
  JAXBContext jaxbContext;
  SchemaFactory factory;
  Schema schema;
  Unmarshaller umsh;
  String xmlfile = "C:/wg/Ort_Elemente.xml";
  try {
    jaxbContext = JAXBContext.newInstance(Orte.class);
    factory = SchemaFactory.newInstance(
              XMLConstants.W3C_XML_SCHEMA_NS_URI);
    schema = factory.newSchema(
             new StreamSource(
                 new File("C:/wg/Ort_Elemente.xsd")));
    umsh = jaxbContext.createUnmarshaller();
    umsh.setSchema(schema);
    Orte x = (Orte)umsh.unmarshal(new File(xmlfile));
    java.util.List listOrte = x.ort;
    for (int i = 0; i < listOrte.size(); i++) {
     Orte.Ort ort = (Orte.Ort) listOrte.get(i);
     System.out.println(ort.getName());
     for(int m=0; m<ort.getMensch().size(); m++){
      Orte.Ort.Mensch mm = ort.getMensch().get(m);
      System.out.println( "\t" + mm.getVorname() 
                         + " " + mm.getName() );
     }
    } 
  } catch (JAXBException e) {
  } catch (SAXException e) {
  }
 }

pic/jaxb.png

Das Resultat:


Neustadt
	Hugo Holzflos
	Nicole Nixlos
	Stefan Sprachlos
	Stefan Sagblos
	Siggi Sorglos
	Heini Herzlos
Darmstadt
	Rudi Rhodos
	Karl Kolos
	Simone Sinnlos
	Horst Hirnlos
	Werner Wertlos
	Ludwig Lustlos
Kapstadt
	Willi Wasistlos
	Rita Ruhelos
	Susi Schlaflos
	Lotte Rielos
	Betty Bodenlos
	Martin Muehelos
	Liane Leinenlos

JAXB und XSD

Im vorliegenden Fall würde das XML-Dokument <ns3:MENSCH xmlns:ns3='wilfried-grupe.de/jaxb'> eingelesen und in ein Java-Objekt der via xjc.exe generierten Klasse überführt werden.

Aber ganz so einfach ist es nicht, weil Sie es hier wieder einmal mit Namespaces zu tun haben, die sich auf drei verschiedene XML-Schema-Dateien verteilen.


<?xml version="1.0" encoding="utf-8"?>
<xs:schema
    elementFormDefault="qualified"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="vorname" type="xs:string"/>
  <xs:element name="nachname" type="xs:string"/>
  <xs:element name="NN" type="xs:string"/>
  <xs:element name="VN" type="xs:string"/>
  <xs:element name="age" type="xs:nonNegativeInteger"/>
</xs:schema>

Die erste XML-Schema-Datei, ich nenne sie Mensch_jaxb_2.xsd, definiert einige Standard-Elemente mit Standard-Typen; hier ist noch kein Target-Namespace deklariert.


<?xml version="1.0" encoding="utf-8"?>
<xs:schema 
    targetNamespace="wilfried-grupe.de/jaxb/demo"
    xmlns:ns2="wilfried-grupe.de/jaxb/demo"
    elementFormDefault="qualified"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:attribute 
      name="HOBBY" 
      type="xs:string"/>
  <xs:attribute 
      name="ALTER1" 
      type="xs:nonNegativeInteger"/>
  <xs:attribute 
      name="ALTER2" 
      type="xs:nonNegativeInteger"/>
</xs:schema>

Die zweite XML-Schema-Datei, ich nenne sie Mensch_jaxb_ns2.xsd, deklariert enige Attribute, aber mit einem Target-Namespace.


<?xml version="1.0" encoding="utf-8"?>
<xs:schema
  targetNamespace="wilfried-grupe.de/jaxb"
  xmlns:ns3="wilfried-grupe.de/jaxb"
  xmlns:ns2="wilfried-grupe.de/jaxb/demo"
    elementFormDefault="qualified"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:import namespace="wilfried-grupe.de/jaxb/demo" 
       schemaLocation="Mensch_jaxb_ns2.xsd"/>
  <xs:import schemaLocation="Mensch_jaxb_2.xsd"/>
  <xs:complexType name="MENSCH_TYP">
    <xs:sequence>
      <xs:element ref="nachname"/>
      <xs:element ref="NN"/>
      <xs:element ref="vorname"/>
      <xs:element ref="VN"/>
    </xs:sequence>
    <xs:attribute ref="ns2:HOBBY" use="required"/>
    <xs:attribute ref="ns2:ALTER1" use="required"/>
    <xs:attribute ref="ns2:ALTER2" use="required"/>
  </xs:complexType>
  <xs:element name="MENSCH" type="ns3:MENSCH_TYP"/>
</xs:schema>

Die dritte XML-Schema-Datei Mensch_jaxb.xsd importiert die beiden anderen XSD-Dateien und arbeitet mit den dort deklarierten Elementen und (Namespace-abhängigen) Attributen.

Nun kommt das Kommando xjc.exe und die automatische Generierung von Java-Klassen ins Spiel.


xjc.exe Mensch_jaxb.xsd

generiert das Java-Package de.wilfried_grupe.jaxb und die Klassen package-info.java, ObjectFactory.java und MENSCHTYP.java (aus Platzgründen spare ich mir den Abdruck des Quellcodes). Der weitere Ablauf ist analog wie oben dargestellt.

Konvertierung zu Person

Die Deserialisierung von XML in Java-Objekte via JAXB funktioniert also, soweit die generierten Objekte von einer bestimmten Klasse sind. Noch offen ist die Deserialisierung eines XML-Inputs in Objekte anderer Klassen, etwa Person: Schließlich soll das XML-Abbild eines Objekts ja (auch) dem Datenaustausch dienen.

Zwar wäre es möglich, die Daten aus dem JAXB-Objekt einzeln in ein Objekt der Klasse Person zu mappen.


de.wilfried_grupe.jaxb.MENSCHTYP jaxbp;
jaxbp = (de.wilfried_grupe.jaxb.MENSCHTYP)
    jaxbUnmarshaller.unmarshal(
        new File("C:/wg/jaxb_demo.xml"));
Person p = new Person();
p.setFirstname(jaxbp.getVorname());
p.setAge(jaxbp.getALTER1().intValue());
p.setLastname(jaxbp.getNachname());

Es gibt aber eine einfachere Alternative: XSLT. Um mittels JAXB ein Objekt der Klasse Person generieren zu können, wird die folgende XML-Struktur benötigt:


<Person>
    <a>33</a>
    <age>33</age>
    <firstname>Wolkenlos</firstname>
    <lastname>Wilma</lastname>
    <NN>Wilma</NN>
    <VN>Wolkenlos</VN>
</Person>

Ein XSL-Stylesheet, das das vorher dargestellte XML-Dokument (ns3:MENSCH xmlns:ns3="wilfried-grupe.de/jaxb) in die Zielstruktur <Person> konvertieren kann, sehen Sie hier:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:ns2="wilfried-grupe.de/jaxb/demo"
  xmlns:ns3="wilfried-grupe.de/jaxb">
  <xsl:output method="xml" indent="yes" />
  <xsl:template match="/">
    <xsl:for-each select="ns3:MENSCH">
      <Person>
        <a><xsl:value-of select="@ns2:ALTER1"/></a>
        <age><xsl:value-of select="@ns2:ALTER2"/></age>
        <firstname><xsl:value-of select="vorname"/></firstname>
        <lastname><xsl:value-of select="nachname"/></lastname>
        <NN><xsl:value-of select="NN"/></NN>
        <VN><xsl:value-of select="VN"/></VN>
      </Person>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Mit der folgenden Java-Logik wird eine XSL-Transformation gestartet; das Ergebnis der Transformation wird umgehend mit JAXB in ein Objekt der Klasse Person übernommen. Dabei war es notwendig, das Ergebnis der Transformation in einen ByteArrayOutputStream zu leiten, um den Output-Stream der XSL-Transformation in einen Input-Stream für JAXB umzuwandeln.


  try {
    TransformerFactory tfactory;
    Transformer transformer;
    InputStream is;
    JAXBContext context;
    tfactory = TransformerFactory.newInstance();
    transformer = tfactory.newTransformer(
      new StreamSource(
        new File("Mensch2Person.xsl")));
    Source s = new StreamSource(
      new File("C:/wg/jaxb_demo.xml"));
    ByteArrayOutputStream outputStream;
    outputStream = new ByteArrayOutputStream();
    Result r = new StreamResult(outputStream);      
    transformer.transform(s, r);
    System.out.println("XSL-Transformation OK");
    is = new ByteArrayInputStream(
      outputStream.toByteArray());
    context = JAXBContext.newInstance(Person.class);
    Unmarshaller um = context.createUnmarshaller();
    Person m = (Person) um.unmarshal(is);
    System.out.println("Instanz von Person erstellt");
    System.out.println(m.getVN());
    System.out.println(m.getNN());
    System.out.println(m.getA());
  } catch (Exception e) {}

Ein Vorteil für den XSLT-Ansatz liegt darin, dass sich die fachlichen Anforderungen der Konvertierungen häufig ändern können. Jede Änderung der Klassen Mensch (Daten-Lieferant) oder Person (Daten-Konsument) kann eine Strukturänderung der gesendeten oder der für JAXB erforderlichen XML-Dokumente nach sich ziehen. Diese Konvertierung kann sehr einfach durch XSLT abgebildet werden.

In diesem Zusammenhang ist vorteilhaft, dass Sie in XSLT mehrere Transformationslogiken einbauen können, die verschiedene XML-Input-Strukturen in ein einheitliches Zielformat konvertieren, das dann via JAXB in Objekte überführt wird. Damit ist der Daten-Consumer auch in der Lage, mehrere Datenlieferanten unterschiedlichen Typs (.NET, PHP ...) anzusprechen.

Ebenso leichter lösbar ist damit das häufige Problem, dass der Datenlieferant seine XML-Struktur teilweise (minor change) oder grundlegend (major change) ändert und die Data-Consumer einen zeitlich punktgenauen Switch der Konvertierungslogik vornehmen müssen.

Ein weiterer Vorteil ist, dass der ursprüngliche Inputstream (also der Output der Mensch-Klasse) keineswegs in XML vorliegen muss, sondern ein anderes Format haben kann, meinetwegen CSV. Es ist in XSLT 2.0 möglich, auch Nicht-XML-Formate einzulesen (unparsed text) und in XML zu konvertieren.

pic/JAXB_XSLT.jpg

Die JAXB vorgeschaltete XSLT-Konvertierung bringt erhebliche Vorteile für die Flexibilität des Datenaustauschs. Ohne die verarbeitenden Klassen selbst ändern zu müssen (Neukompilierung nicht erforderlich), kann die komplette XML-XSL-XML-Konvertierung in einer Vorstufe erfolgen.

Sehr sinnvoll ist, das zu transformierende XML-Dokument "jaxb_demo.xml" vor der XSL-Transformation gegen ein XML-Schema zu validieren. Auf diese Weise sollte sichergestellt werden, dass sich die Struktur des XML-Dokuments nicht grundlegend geändert hat, mit der möglichen Folge eines gravierenden Informationsverlustes.

wg / 14. Oktober 2018



Fragen? Anmerkungen? Tipps?

Bitte nehmen Sie Kontakt zu mir auf.






Vielen Dank für Ihr Interesse an meiner Arbeit.


V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/JAXB.html