JAXB – Java Architecture for XML Binding

veröffentlicht am 10. Februar 2012

Heute möchte ich euch JAXB, die Java Architecture for XML Binding vorstellen. Mit JAXB lassen sich spielend einfach Java Objekte in XML umwandeln und vice versa. Dieser Artikel gibt eine kurze Einführung in dieses tolle Framework, was übrigens im Standardumfang von Java enthalten ist.

Zur Erläuterung nehme ich das allseits beliebte Bücher-Beispiel. Um das Beispiel nicht unnötig aufzublähen, beschränken wir uns darauf, ein Buch durch dessen Titel, seine Autoren, den Verlag und die ISBN (13) abzubilden. Weiterhin werden diese Bücher in ein nicht näher spezifiziertes Bücherregal gestellt. Mit den entsprechenden Annotations ergeben sich folgende Klassen:

package de.patrickgotthard.jaxb.books;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;

public class Book {

	private String title;
	private List<String> authors = new ArrayList<String>();
	private String publisher;
	private String isbn;

	public String getTitle() {
		return title;
	}

	public void setTitle(final String title) {
		this.title = title;
	}

	@XmlElementWrapper(name = "authors")
	@XmlElement(name = "author")
	public List<String> getAuthors() {
		return authors;
	}

	public void setAuthors(final List<String> authors) {
		this.authors = authors;
	}

	public String getPublisher() {
		return publisher;
	}

	public void setPublisher(final String publisher) {
		this.publisher = publisher;
	}

	public String getIsbn() {
		return isbn;
	}

	public void setIsbn(final String isbn) {
		this.isbn = isbn;
	}

}

In dieser Klasse findest du die Annotationen @XMLElementWrapper und @XMLElement. Die erste Annotation ist dafür bestimmt, um Listen in XML mit einem Tag zu umschließen (in diesem Fall <authors></authors>). @XMLElement gibt dann an, wie die einzelnen Elemente abgebildet werden sollen (hier durch <author></author>).

package de.patrickgotthard.jaxb.books;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class BookShelf {

	private List<Book> books = new ArrayList<Book>();

	@XmlElement(name = "book")
	public List<Book> getBooks() {
		return books;
	}

	public void setBooks(final List<Book> books) {
		this.books = books;
	}

}

In dieser Klasse sind folgende Dinge zu beachten:

  • Die Annotation @XMLRootElement gibt an, dass diese Klasse als Wurzelelement (oberste XML-Ebene) verwendet werden kann.
  • Die Bücherliste wurde diesmal nur mit @XMLElement versehen. Hierdurch werden zwar alle Bücher im XML ausgegeben, allerdings nicht durch ein anderes Tag umschlossen.
  • Zuletzt wurde die toString() Methode überschrieben, damit man gleich eine einfache Testklasse schreiben kann.

Weiterhin muss nun noch beachtet werden, eine Textdatei namens jaxb.index in demselben Package abzulegen. In dieser Datei müssen alle verwendeten RootElemente angegeben werden, in diesem Beispiel also:

BookShelf

Um die Funktionsweise von JAXB zu erläutern, habe ich folgende Klasse geschrieben:

package de.patrickgotthard.jaxb.books;

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Main {

	public static void main(final String[] args) throws JAXBException {

		// SCJP Buch
		final Book scjpBook = new Book();
		scjpBook.setTitle("SCJP Sun Certified Programmer for Java 6 Study Guide");
		scjpBook.getAuthors().add("Katherine Sierra");
		scjpBook.getAuthors().add("Bert Bates");
		scjpBook.setPublisher("Mcgraw-Hill");
		scjpBook.setIsbn("978-0071591065");

		// Java Insel
		final Book javaInselBook = new Book();
		javaInselBook.setTitle("Java ist auch eine Insel: Das umfassende Handbuch");
		javaInselBook.getAuthors().add("Christian Ullenboom");
		javaInselBook.setPublisher("Galileo Computing");
		javaInselBook.setIsbn("978-3836218023");

		// Bücherregal befüllen
		BookShelf bookShelf = new BookShelf();
		bookShelf.getBooks().add(scjpBook);
		bookShelf.getBooks().add(javaInselBook);

		// JAXBContext erzeugen
		final JAXBContext context = JAXBContext.newInstance("de.techspread.jaxb.books");

		// Objekt in XML umwandeln (Marshalling)
		final Marshaller marshaller = context.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		marshaller.marshal(bookShelf, new File("bookshelf.xml"));

		// XML in Objekt umwandeln (Unmarshalling)
		final Unmarshaller unmarshaller = context.createUnmarshaller();
		bookShelf = (BookShelf) unmarshaller.unmarshal(new File("bookshelf.xml"));

		// Inhalte des Bücherregals ausgeben
		for (final Book book : bookShelf.getBooks()) {
			System.out.format("%s, %s, %s, %s\n", book.getTitle(), book.getAuthors(), book.getPublisher(), book.getIsbn());
		}
	}
}

Beim Erzeugen des JAXBContext muss darauf geachtet werden, das Package anzugeben, in dem sich die annotierten JAXB-Klassen befinden. Mehrere Packages werden durch einen Doppelpunkt voneinander getrennt. Beim Marshaller wird die Property JAXB_FORMATTED_OUTPUT auf true gesetzt. Dadurch wird die XML-Datei vernünftig lesbar formatiert. Führst du die Testklasse nun aus, sollte folgende Ausgabe in der Konsole erscheinen:

SCJP Sun Certified Programmer for Java 6 Study Guide,[Katherine Sierra, Bert Bates],Mcgraw-Hill,978-0071591065
Java ist auch eine Insel: Das umfassende Handbuch,[Christian Ullenboom],Galileo Computing,978-3836218023

Weiterhin sollte die XML-Datei bookshelf.xml folgenden Inhalt besitzen:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bookShelf>
    <book>
        <authors>
            <author>Katherine Sierra</author>
            <author>Bert Bates</author>
        </authors>
        <isbn>978-0071591065</isbn>
        <publisher>Mcgraw-Hill</publisher>
        <title>SCJP Sun Certified Programmer for Java 6 Study Guide</title>
    </book>
    <book>
        <authors>
            <author>Christian Ullenboom</author>
        </authors>
        <isbn>978-3836218023</isbn>
        <publisher>Galileo Computing</publisher>
        <title>Java ist auch eine Insel: Das umfassende Handbuch</title>
    </book>
</bookShelf>

Sollen Objekteigenschaften als Attribute im XML abgebildet werden, wird der entsprechende getter einfach mit @XMLAttribute versehen. So könnte ein Buch zum Beispiel wie folgt abgebildet werden:

<book title="SCJP Sun Certified Programmer for Java 6 Study Guide" publisher="Mcgraw-Hill" isbn="978-0071591065">
	<authors>
		<author>Katherine Sierra</author>
		<author>Bert Bates</author>
	</authors>
</book>

Weiterhin sollte darauf geachtet werden, immer die getter zu annotieren, ansonsten fliegt eine IllegalAnnotationException, da die JAXB-Annotations dafür sorgen, dass die annotierte Variable oder Methode öffentlich sichtbar ist und somit zwei „Variablen“ desselben Namens verfügbar sind (diese Erkenntnis hat mich einige Zeit gekostet).

Kommentare

Schöne Sache, benutze ich sehr gerne. Ist nicht nur einfach zu implementieren, auch das erzeugte XML ist gut lesbar und lässt sich auch leicht wieder durch andere Programmen weiterverarbeiten.

Kommentar #1 von FERNmann am 10. Februar 2012


OFFTOPIC:
———–
Wenn man den StringBuilder nutzt, sollte man dies aber auch wirklich konsequent nutzen, sonst kann man`s auch gleich sein lassen (gemeint ist die toString) :P
Wieso schreibst du dir nicht auch viel eher eine toString in Book? ;/

Kommentar #2 von Basti am 11. Februar 2012


Hast recht, hätte die Ausgabe einfach in der Testklasse implementieren oder konsequent auch toString() in Book überschreiben können.

Kommentar #3 von Patrick am 11. Februar 2012


ich meinte eig. das „builder.append(book.getTitle() + „,“);“ usw… (bzw book.getTitle() + „,“)

Kommentar #4 von Basti am 11. Februar 2012


Achso meinst du das… stimmt auch wieder, da habe ich garnicht drauf geachtet :) Ich denke, den kleinen Overhead vertragen wir heute einmal ;)

Kommentar #5 von Patrick am 11. Februar 2012


Für Basti den Nörgler habe ich das Beispiel jetzt abgerundet ;)

Kommentar #6 von Patrick am 17. Februar 2012


Hallo Patrick,

sehr schön und übersichtlich, hilft beim Verständnis :)

ich bin als Java-Neuling auf deinen Blog hier gestoßen, weil ich für ein aktuelles Projekt auch eine XML-Datei erzeugen möchte (bzw. muss, Vorgabe vom Prof.). Im Prinzip sieht das Ganze auch ähnlich aus wie deinem Beispiel und es funktioniert auch weitesgehend, aber:

In meinem Projekt habe ich eine ArrayList, die nicht wie bei dir mit Strings gefüllt ist, sondern mit Objekten aus einer zusätzlichen Klasse. Ich habe es mit einer String-ArrayList ausprobiert, die wird ohne Probleme in XML ausgegeben, aber die mit komplexeren Objekten gefüllte ArrayList taucht im XML-Baum nicht auf. Lediglich de umschließende Notation (also der Name der List) aber ohne Inhalt.

Der Code ist ziemlich lang, daher poste ich ihn hier nicht, vllt fällt dir ja so spontan schon ein Grund ein?

Danke schonmal! :)

Kommentar #7 von Julian am 27. Mai 2012


Julian, die Attribute deiner Klasse, deren Objekte in der Array-List enthalten sind, müssen ebenfalls mit Annotationen ausgestattet sein. Dasselbe Symptom hatte ich auch, als ich das mal vergessen hatte.

Kommentar #8 von FERNmann am 28. Mai 2012


Hallo FERNmann,

leider scheint es daran nicht zu liegen und seltsamerweise egal welche Annotation ich den Elementen der „Behelfsklasse“ gebe, es wird immer dieser Fehler angezeigt: javax.xml.bind.MarshalException

Es ist bei mir so, dass ich ein RootElement habe (eine ArrayList) diese enthält eine Reihe von Objekten einer anderen Klasse „Szene“. „Szene“ hat mehrere Strings (die werden auch richtig serialisiert) und eben auch eine List mit Objekten, die ihrerseits jeweils einen String haben und eine Instanz der Klasse „Szene“.

Ursprünglich hatte ich statt diesem Umweg eine HashMap genommen, habe ich gelesen, dass JAXB mit HashMaps nichts anfangen kann…

Kommentar #9 von Julian am 28. Mai 2012


Der Versuch das Problem textlich zu schildern hat mich gerade selbst auf den Fehler aufmerksam gemacht.

Wenn ich in der ArrayList auf der untersten Ebene im XML-Baum eine Instanz einer Klasse in einer höheren Hierachie enthalten habe, muss es ja zwangsläufig zu einem unendlich tiefen XML-Baum kommen. Ich sollte mir also einfach eine andere Möglichkeit überlegen ohne die Klasse in der tieferen Ebene erneut anzusprechen.

Manchmal steht man gehörig auf dem Schlauch. Aber vielen Dank für die Hilfsbereitschaft. :)

Kommentar #10 von Julian am 28. Mai 2012


Hallo Julian,

da scheint es eine Designschwäche bei deinen Klassen zu geben, du hast eine zyklische Abhängigkeit codiert ;)

Maps können auch marshalled werden. Siehe:
http://jaxb.java.net/guide/Mapping_your_favorite_class.html
http://www.java-forum.org/xml-co/116454-jaxb-hashmap.html
http://stackoverflow.com/questions/5317172/how-to-use-hashmap-properties-with-jaxb

Kommentar #11 von Patrick am 28. Mai 2012


Ja, das hab ich wohl. Das Licht ist mir nur leider viel zu spät aufgegangen. ;)

Danke für die Tipps :)

Kommentar #12 von Julian am 28. Mai 2012


Möglicherweise hilft dir auch die Annotation @XmlTransient weiter. Damit kann man einzelne Attribute aus dem XML-Baum ausschließen. Musst du natürlich beim unmarshallen wieder berücksichtigen.

Kommentar #13 von FERNmann am 28. Mai 2012


Gibt es eigentlich auch irgendeinen schönen Weg zyklische Abhängigkeiten abzubilden? Ab und an ist das ja auch gewollt. Einfaches Beispiel: Personen die miteinander verheiratet sind:

class Person {
public Person marriedWith;
}

Person a = new Person();
Person b = new Person();
a.marriedWith = b;
b.marriedWith = a;

(hab jetzt absichtlich keine Getter und Setter verwendet)

Wie kann ich damit umgehen?

Vielen Dank für die Hilfe :)
lg
Christian.

Kommentar #14 von Christian am 21. November 2012


Hallo Christian,

zyklische Abhängigkeiten will man IMMER vermeiden!

In deinem Beispiel würde ich einfach die Klasse „Ehepaar“ erstellen, das die beiden Personen aufnimmt. Somit ist die zyklische Abhängigkeit aus dem Weg.

Schon dass JAXB zyklische Abhängigkeiten nicht ohne weiteres abbilden kann, ist Zeichen genug dafür, dass man diese vermeiden sollte :)

Gruß
Patrick

Kommentar #15 von Patrick am 21. November 2012


Hy Patrick!

Danke für die Info. Bezüglich der zyklischen Referenzen: Normalerweise würde man immer versuchen so etwas zu vermeiden, nur manchmal geht es einfach nicht: In meinem Fall bekomme ich ein Objekt, dass für mich unveränderlich ist und ich muss es über SOAP transportieren. Habe es jetzt mit XMLID und XMLREFID gelöst. Der Pferdefuß an der Methode ist leider nur, dass JAX-B nicht automatisch die Objekte, die referenziert werden mit marshalled, sondern ich brauche irgendeine (Wrapper) Klasse, die die referenzierten Objekte beinhalten.

Vielen Dank, lg
Christian.

Kommentar #16 von Christian am 30. November 2012


Hinterlasse einen Kommentar