XML-Verarbeitung mit XStream

Wir nutzen bei sLAB schon seit langem XStream als Möglichkeit XML zu serialisieren und deserialisieren. Die Bibliothek ist seit Dezember 2008 unverändert und ich bin im Moment wieder dabei, damit zu arbeiten.

Setup

Für das Tutorial benötigen wir erst einmal ein paar Klassen zum serialisieren. Wir beginnen einfach mit zwei Klassen Angestellter und Adresse.

package de.billmann.xstream;
public class Angestellter {
  private Long personalNummer;
  private String vorname;
  private String nachname;
  private Adresse adresse;
  private long internerWert;
  public Angestellter() {
    this.internerWert = System.currentTimeMillis();
  }
  public Long getPersonalNummer() {
    return personalNummer;
  }
  public void setPersonalNummer(Long personalNummer) {
    this.personalNummer = personalNummer;
  }
  public String getVorname() {
    return vorname;
  }
  public void setVorname(String vorname) {
    this.vorname = vorname;
  }
  public String getNachname() {
    return nachname;
  }
  public void setNachname(String nachname) {
    this.nachname = nachname;
  }
  public Adresse getAdresse() {
    return adresse;
  }
  public void setAdresse(Adresse adresse) {
    this.adresse = adresse;
  }
}
package de.billmann.xstream;
public class Adresse {
  private String strasse;
  private String plz;
  private String ort;
  public String getStrasse() {
    return strasse;
  }
  public void setStrasse(String strasse) {
    this.strasse = strasse;
  }
  public String getPlz() {
    return plz;
  }
  public void setPlz(String plz) {
    this.plz = plz;
  }
  public String getOrt() {
    return ort;
  }
  public void setOrt(String ort) {
    this.ort = ort;
  }
}

Bei der Klasse Angestellter habe ich noch zur Demonstration ein Feld hinzugefügt, welches nicht über getter/setter erreichbar ist, um zu zeigen, daß die Bibiothek auf den Klassenvariablen arbeitet.

Erste Verwendung

Als erstes wandeln wir einmal einen Angestellten ohne weitere konfiguration in XML um.

package de.billmann.xstream;
import com.thoughtworks.xstream.XStream;
public class XStreamTest {
  public static void main(String[] args) {
    XStream xstream = new XStream();
    // Einen Angestellten erzeugen
    Angestellter angestellter1 = new Angestellter();
    angestellter1.setPersonalNummer(123456l);
    angestellter1.setVorname("Hans");
    angestellter1.setNachname("Mustermann");
    System.out.println(xstream.toXML(angestellter1));
  }
}

Als Ergebnis erhalten wir

<de.billmann.xstream.Angestellter>
  <personalNummer>123456</personalNummer>
  <vorname>Hans</vorname>
  <nachname>Mustermann</nachname>
  <internerWert>1299606058760</internerWert>
</de.billmann.xstream.Angestellter>

Für den Anfang schonmal ganz brauchbar. Aber jetzt wollen wir mal anschauen wie die eine Referenz aufgelöst wird und dazu fügen wir dem Angstellten eine Adresse hinzu.

package de.billmann.xstream;
import com.thoughtworks.xstream.XStream;
public class XStreamTest {
  public static void main(String[] args) {
    XStream xstream = new XStream();
    // Einen Angestellten erzeugen
    Angestellter angestellter1 = new Angestellter();
    angestellter1.setPersonalNummer(123456l);
    angestellter1.setVorname("Hans");
    angestellter1.setNachname("Mustermann");
    // Eine Adresse dazu erzeugen
    Adresse adresse1 = new Adresse();
    adresse1.setStrasse("Daheimweg");
    adresse1.setPlz("12345");
    adresse1.setOrt("Zuhause");
    angestellter1.setAdresse(adresse1);
    System.out.println(xstream.toXML(angestellter1));
  }
}

Das Ergebnis sieht folgendermaßen aus:

<de.billmann.xstream.Angestellter>
  <personalNummer>123456</personalNummer>
  <vorname>Hans</vorname>
  <nachname>Mustermann</nachname>
  <adresse>
    <strasse>Daheimweg</strasse>
    <plz>12345</plz>
    <ort>Zuhause</ort>
  </adresse>
  <internerWert>1299606190037</internerWert>

Liste von Angestellten

Häufig hat man ja nicht nur ein Objekt sondern eine ganze Liste, die man serialisieren möchte und das testen wir jetzt.

package de.billmann.xstream;
import com.thoughtworks.xstream.XStream;
import java.util.ArrayList;
import java.util.List;
public class XStreamTest {
  public static void main(String[] args) {
    XStream xstream = new XStream();
    List firma = new ArrayList();
    // Einen Angestellten erzeugen
    Angestellter angestellter1 = new Angestellter();
    angestellter1.setPersonalNummer(123456l);
    angestellter1.setVorname("Hans");
    angestellter1.setNachname("Mustermann");
    // Eine Adresse dazu erzeugen
    Adresse adresse1 = new Adresse();
    adresse1.setStrasse("Daheimweg");
    adresse1.setPlz("12345");
    adresse1.setOrt("Zuhause");
    angestellter1.setAdresse(adresse1);
    // Eine Angestellte erzeugen
    Angestellter angestellter2 = new Angestellter();
    angestellter2.setPersonalNummer(654321l);
    angestellter2.setVorname("Giesela");
    angestellter2.setNachname("Beispiel");
    firma.add(angestellter1);
    firma.add(angestellter2);
    System.out.println(xstream.toXML(firma));
  }
}

Als Ausgabe erhalten wir:

<list>
  <de.billmann.xstream.Angestellter>
    <personalNummer>123456</personalNummer>
    <vorname>Hans</vorname>
    <nachname>Mustermann</nachname>
    <adresse>
      <strasse>Daheimweg</strasse>
      <plz>12345</plz>
      <ort>Zuhause</ort>
    </adresse>
    <internerWert>1299606601940</internerWert>
  </de.billmann.xstream.Angestellter>
  <de.billmann.xstream.Angestellter>
    <personalNummer>654321</personalNummer>
    <vorname>Giesela</vorname>
    <nachname>Beispiel</nachname>
    <internerWert>1299606601941</internerWert>
  </de.billmann.xstream.Angestellter>
</list>

Ausgabe aufhübschen

Das meiste kann XStream aus dem Zusammenhang der Klassen entnehmen. So werden die einzelnen Klassenvariablen als Namen für die tags verwendet. Nur bei der Klasse Angestellter funktioniert das nicht. Für eine Deserialisierung hätte XStream nicht die nötigen Informationen, die richtige Klasse zu instanziieren (angenommen es gäbe mehrere Klassen Angestellter). Bei der Adresse ist es unstrittig, da die Klassendefinition von Angestellter ja den genaun Typ enthält.
Allerdings ist die Ausgabe mit package nicht sonderlich schön aber hierzu bietet uns XStream einfache Möglichkeiten das zu konfigurieren.

package de.billmann.xstream;
import com.thoughtworks.xstream.XStream;
public class XStreamTest {
  public static void main(String[] args) {
    XStream xstream = new XStream();
    // Einen Angestellten erzeugen
    Angestellter angestellter1 = new Angestellter();
    angestellter1.setPersonalNummer(123456l);
    angestellter1.setVorname("Hans");
    angestellter1.setNachname("Mustermann");
    // class mapping
    xstream.alias("angestellter", Angestellter.class);
    System.out.println(xstream.toXML(angestellter1));
  }
}

Wir können für Klassen einen Alias angeben, der dann eindeutig mit dieser Klasse verknüpft ist. Sowohl zur Serialisierung als auch zur Deserialisierung nutzt extrem diesen Namen.

<angestellter>
  <personalNummer>123456</personalNummer>
  <vorname>Hans</vorname>
  <nachname>Mustermann</nachname>
  <internerWert>1299607040128</internerWert>
</angestellter>

Es können hierbei nicht nur die Klassennamen mit einem Alias versehen werden, man kann auch die Klassenvariablen einen Alias zuweisen. Dadurch hat man die Möglichkeit das XML noch zu beeinflussen, falls man bestimmte Vorgaben hat, weil es zum DAtenaustausch mit einem anderen System dient.

package de.billmann.xstream;
import com.thoughtworks.xstream.XStream;
public class XStreamTest {
  public static void main(String[] args) {
    XStream xstream = new XStream();
    // Einen Angestellten erzeugen
    Angestellter angestellter1 = new Angestellter();
    angestellter1.setPersonalNummer(123456l);
    angestellter1.setVorname("Hans");
    angestellter1.setNachname("Mustermann");
    // class mapping
    xstream.alias("angestellter", Angestellter.class);
    // field mapping
    xstream.aliasField("pn", Angestellter.class, "personalNummer");
    System.out.println(xstream.toXML(angestellter1));
  }
}

Hierbei wird das tag personalNummer in pn gewandelt.

<angestellter>
  <pn>123456</pn>
  <vorname>Hans</vorname>
  <nachname>Mustermann</nachname>
  <internerWert>1299622340068</internerWert>
</angestellter>

Klassenvariablen auslassen

Der interne Wert soll nicht serialisiert werden. Um das zu erreichen kann man einfach XStream mit omitField davon in Kenntnis setzen.

package de.billmann.xstream;
import com.thoughtworks.xstream.XStream;
public class XStreamTest {
  public static void main(String[] args) {
    XStream xstream = new XStream();
    // Einen Angestellten erzeugen
    Angestellter angestellter1 = new Angestellter();
    angestellter1.setPersonalNummer(123456l);
    angestellter1.setVorname("Hans");
    angestellter1.setNachname("Mustermann");
    // class mapping
    xstream.alias("angestellter", Angestellter.class);
    // field mapping
    xstream.aliasField("pn", Angestellter.class, "personalNummer");
    // omit field
    xstream.omitField(Angestellter.class, "internerWert");
    System.out.println(xstream.toXML(angestellter1));
  }
}

Und nun taucht der Wert nicht mehr auf.

<angestellter>
  <pn>123456</pn>
  <vorname>Hans</vorname>
  <nachname>Mustermann</nachname>
</angestellter>

Attribute hinzufügen

Nicht immer ist es gewünscht einen Wert als eigenständiges tag anzugeben. Im nachfolgenden Beispiel wird die personalNummer, welche jetzt mit pn abgekürzt wird, als attribute angegeben.

package de.billmann.xstream;
import com.thoughtworks.xstream.XStream;
public class XStreamTest {
  public static void main(String[] args) {
    XStream xstream = new XStream();
    // Einen Angestellten erzeugen
    Angestellter angestellter1 = new Angestellter();
    angestellter1.setPersonalNummer(123456l);
    angestellter1.setVorname("Hans");
    angestellter1.setNachname("Mustermann");
    // class mapping
    xstream.alias("angestellter", Angestellter.class);
    // field mapping
    xstream.aliasField("pn", Angestellter.class, "personalNummer");
    // omit field
    xstream.omitField(Angestellter.class, "internerWert");
    // as attribute
    xstream.useAttributeFor(Angestellter.class, "personalNummer");
    System.out.println(xstream.toXML(angestellter1));
  }
}
<angestellter pn="123456">
  <vorname>Hans</vorname>
  <nachname>Mustermann</nachname>
</angestellter>

Fazit

Das Tutorial sollte zeigen wie einfach es mit XStream ist, mit XML umzugehen. Die Bibliothek kann weitaus mehr und es lohnt sich sie anzusehen. Die Dokumentation und Beispiele auf der Projektseite sind umfangreich und leicht nach zu vollziehen. Bei sLAB setzen wir die Bibliothek ein um XML-Konfigurationen einzulesen oder um XML als Austauschformat mit anderen Systemen zu nutzen, ohne uns mit aufwändigem parsen herum zu schlagen.

Comments