Als allgegenwärtiges Austauschformat ist XML gut in Java implementiert. Aber diese Implementierungen verbergen, wie sie die Datenbindung von einer XML-Struktur zu einem Objektgraphen durchführen. Damit stehen wir hilflos vor einer Anwendung, die XML als einfachen alten String ausgibt. Da die Low-Level-API (DOM, XPath), die sich auf die Dokumentenstruktur konzentriert, mühsam ist, haben die wichtigsten JAX-RS-Implementierungen (Jersey, CXF) die gleiche High-Level-API gewählt, die sich auf die Daten konzentriert: JAXB. Lassen Sie uns das Gleiche tun.
Beschreiben Sie ein Austauschformat
Wenn der XML-Produzent der Ausgabe über keinen Mechanismus zur Beschreibung seiner Knotenstruktur verfügt, müssen seine Konsumenten einen Weg wählen, um bestmöglich zu arbeiten. Dies kann mit einem Objektgraphen geschehen, der dieser XML-Ausgabe entspricht. Lassen Sie uns dies anhand eines köstlichen Dessertbeispiels sehen.
[xml]
<rezept name="Birnenkompott" type="Dessert">
<kochen dauer="15">
<step optional="true">Lassen Sie eine Vanilleschote beiseite</step>
<Schritt>Birnen schälen und entkernen</Schritt>
<step>Birnen in Scheiben schneiden</step>
<step>Geben Sie diese Scheiben in eine Kasserolle mit Wasser</step>
</cooking>
<Menü>17-02-2011</menu>
<Menü>17-03-2011</menu>
</recipe>
[/xml]
JAXB identifiziert jeden Knoten als ein Element mit Attributen. Ein Element ist ein komplexer Typ mit einer Elementsequenz (immer zuerst) und einer Attributliste. Ein Element kann einen einfachen Textwert haben, wenn es keinen Unterknoten hat. Ein Attribut hat nur einen Textwert. Jeder XML-Knoten wird durch ein Element und, mit XJC, durch ein gleichnamiges Objekt dargestellt, das aus dem Schema generiert wird (die XJC-Generierung wird im Anhang erläutert).
Hier ist die XSD-Beschreibung des vorherigen XML:
[xml]
<xsd:schema xmlns_xsd="https://www.w3.org/2001/XMLSchema">
<xsd:element name="rezept">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="menu" type="xsd:string" maxOccurs="unbounded" />
<xsd:element name="kochen" type="kochen" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:complexType name="Kochen">
<xsd:sequence>
<xsd:element name="step" type="step" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="dauer" type="xsd:int" />
</xsd:complexType>
<xsd:complexType name="step" mixed="true">
<xsd:attribute name="optional" type="xsd:boolean" />
</xsd:complexType>
</xsd:schema>
[/xml]
Diesen Schemaknoten ist das Präfix " xsd " vorangestellt, da ihre Definition in der ersten Zeile einen XML-Namensraum (xmlns) verwendet. Namespaces sind eine Möglichkeit, Header-Deklarationen voneinander zu unterscheiden, wie es ein Java-Paket tun würde.
Variablen werden gemäß der w3c-Empfehlung typisiert. Wenn eine primitiv typisierte Liste verwendet wird (Menüknoten), wird kein neues Objekt benötigt. Ein Objekt wird jedoch benötigt (step node), wenn sowohl ein Wert als auch einige Attribute vorhanden sind. Standardmäßig ist es entweder das eine oder das andere, aber nicht beides.
Hier ist der generierte Code aus dem Schema:
[java]
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(Name = "Rezept")
public class Recipe {
geschützte Liste<Zeichenfolge> Menü;
Geschütztes Kochen Kochen;
@XmlAttribut
protected String name;
@XmlAttribut
protected String Typ;
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Kochen")
public class Cooking {
geschützte Liste<Schritt> Schritt;
@XmlAttribut
protected Integer Dauer;
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "step")
public class Step {
@XmlValue
protected String Inhalt;
@XmlAttribut
protected Boolean optional;
}
[/java]
Nur eine Klasse ist mit @XmlRootElement annotiert. Es ist die einzige (in diesem Schema), die der erste Knoten der Ausgabe sein kann. Sobald der Objektgraph generiert ist, genügen 3 Zeilen Code, um einen XML-String zu parsen:
[java]
public class JaxbTest {
@Test
public void should_parse_recipe() throws JAXBException {
URL xmlUrl = Resources.getResource("recipe.xml");
Recipe recipe = parse(xmlUrl, Recipe.class);
assertEquals(Integer.valueOf(15), recipe.getCooking().getDuration());
}
private <T> T parse(URL url, Class<T> clazz) throws JAXBException {
Unmarshaller unmarsh = JAXBContext.newInstance(clazz).createUnmarshaller();
return clazz.cast(unmarsh.unmarshal(url));
[/java] }
}
Im Vergleich zur Low-Level-API ist keine Konvertierung erforderlich: die Deklaration von Typen im Schema reicht aus. Im Falle eines Konvertierungsfehlers (ein String, der nicht in einen Typ konvertiert werden kann) wird eine entsprechende IllegalArgumentException ausgelöst. Wenn ein Knoten fehlt, sind die entsprechenden Variablen null.
Verfeinern Sie das Austauschformat
Sobald die Datenbindung erfolgreich ist, ist mehr Kontrolle erforderlich:
- einen Wert auf eine Reihe von Möglichkeiten einschränken;
- manipulieren Sie das Java-Datum anstelle des standardmäßig von JAXB verwendeten XMLGregorianCalendar;
- einen anderen Klassennamen verwenden, um ein Element darzustellen;
- Objektvererbung zwischen Elementen verwenden;
- vorhandene Klassen manuell annotieren.
Einen Wert auf eine Reihe von Möglichkeiten einschränken
So können Sie das Attribut " type " auf " starter, main, desert " beschränken: [xml] <xsd:element name="rezept"> <xsd:attribute name="type" type="Kurs" /> </xsd:element> <xsd:simpleType name="Kurs"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="starter"></xsd:enumeration> <xsd:enumeration value="main"></xsd:enumeration> <xsd:enumeration value="Wüste"></xsd:enumeration> </xsd:restriction> </xsd:simpleType> [/xml] Daraus ergibt sich, wenn generiert: [java] @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "recipe") public class Recipe { @XmlAttribute protected Kursart; } @XmlEnum public enum Course { @XmlEnumValue("starter") STARTER("starter"), @XmlEnumValue("main") MAIN("main"), @XmlEnumValue("desert") DESERT("desert"); private final String value; } [/java] Wenn beim Binden der Wert mit keiner der aufgeführten Möglichkeiten übereinstimmt, wird standardmäßig ein Nullwert verwendet. Gemäß der Konvention verwendet JAXB einen Nullwert, wenn ein Problem auftritt, und löst eine Ausnahme für Probleme bei der Eingabe aus.
Java-Daten verwenden
Um ein einfaches Datum anstelle des standardmäßig in JAXB verwendeten XMLGregorianCalendar zu verwenden, ist ein Konverter erforderlich. Seien Sie vorsichtig, ein neuer Namespace wurde für das Root-Element deklariert. Er ermöglicht es uns, den Standardtyp Datum neu zu definieren.
[xml]
<xsd:schema xmlns_xsd="https://www.w3.org/2001/XMLSchema"
xmlns_jxb="https://java.sun.com/xml/ns/jaxb"
jxb_version="2.0">
<xsd:annotation><xsd:appinfo>
<jxb:globalBindings>
<jxb:javaType name="java.util.Date" xmlType="xsd:dateTime"
parseMethod="com.xebia.jaxb.JaxbDateConverter.parseDate" />
</jxb:globalBindings>
</xsd:appinfo></xsd:annotation>
<xsd:element name="menü" type="xsd:dateTime" />
</xsd:schema>
[/xml]
Der Konverter muss sich an die JAXB-Konventionen halten. Wenn der Wert nicht passt, wird keine Ausnahme ausgelöst, sondern stattdessen ein Nullwert zurückgegeben.
[java]
public class JaxbDateConverter {
public static Date parseDate(String s) {
DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
try {
return formatter.parse(s);
} catch (ParseException e) {
return null;
[/java] }
}
}
Bei der Erzeugung von Klassen erstellt JAXB eine Adapterklasse, die an die statischen Methoden des Konverters gebunden ist.
[java]
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "recipe")
public class Recipe {
@XmlElement(type = String.class)
@XmlJavaTypeAdapter(Adapter1.class)
@XmlSchemaType(name = "dateTime")
protected Liste<Datum> menu;
[/java]
Verwenden Sie einen anderen Klassennamen für das Element
Um eine Klasse zu benennen, die auf das dargestellte Element verschoben wird, genügt es, das Schema auf folgende Weise zu ändern (achten Sie darauf, beide Namensräume hinzuzufügen): [xml] <xsd:element name="menü" type="xsd:dateTime" /> <xsd:annotation><xsd:appinfo> <jxb:class name="Mahlzeit" /> </xsd:appinfo></xsd:annotation> [/xml]
Objektvererbung verwenden
Die Vererbung von Objekten ist einfach (generierte Objekte vererben sich natürlich gegenseitig): [xml] <xsd:complexType name="menuxl"> <xsd:complexInhalt> <xsd:extension base="menu" /> </xsd:complexContent> <xsd:attribute name="kochen" type="xsd:string" /> </xsd:complexType> [/xml]
Vorhandene Klassen manuell annotieren
Bis jetzt haben wir nur dank des Schemas generierte Klassen verwendet. Aber wir können eine Klasse auch manuell annotieren, um das XML zu binden. Ein Schema kann sogar aus dem kommentierten Quellcode generiert werden. Manuelle Anmerkungen geben mehr Kontrolle über Objekte, ihren Typ und die Möglichkeit, bereits vorhandene zu verwenden, die für andere Zwecke verwendet wurden (das Hinzufügen eines nicht bindbaren Attributs kann mit @XmlTransient erfolgen). Auch hier müssen wir uns an die JAXB-Konvention halten. In seinem generierten Code überlädt JAXB die Getter, um leere Listen anstelle von Null zurückzugeben. Es wird empfohlen, dies ebenfalls zu tun. [java] public List<String> getMenu() { if (menu == null) { menu = new ArrayList<String>(); } return menu; } [/java]
Epilog: Austauschformat auf dem Server
Bisher sind wir davon ausgegangen, dass kein Mechanismus zur Beschreibung der Knotenorganisation vom Produzenten zum Konsumenten zur Verfügung gestellt wurde; dies wurde schließlich durch Austauschformate gelöst. Wenn möglich, ist es besser, diese Einschränkung aufzuheben, indem man den Verbrauchern ein Schema mit Daten zur Verfügung stellt. Um dies zu erreichen, ist es ratsam, sich für eine vertragsorientierte Entwicklung zu entscheiden. Um die Kopplung zwischen dem Schema und seiner Implementierung zu begrenzen, ist es ratsam, Objekte auf der Serverseite zu generieren, anstatt sie zu annotieren. Die Einhaltung der Grenzen der Typen und der Organisation eines XSD-Schemas garantiert die Kompatibilität der Ausgabe mit jeder Art von Client (insbesondere mit Nicht-Java-Clients). Die Spring-Referenzdokumentation befürwortet diese Idee.
Anhang: Tools
Um die Beispiele ausführen zu können, benötigen Sie Folgendes: JAXB Maven-Abhängigkeit und das zugehörige Generierungs-Plugin. Um das Testen zu erleichtern, stellen wir auch eine einfache Plugin-Konfiguration zur Verfügung (die Generierung kann mit mvn jaxb2:xjc gestartet werden): [xml] <Plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>jaxb2-maven-plugin</artifactId> <Konfiguration> <outputDirectory>${basedir}/src/main/java</outputDirectory> <schemaDirectory>${basedir}/src/main/resources/xsd</schemaDirectory> <Paketname>com.xebia.jaxb.generated</Paketname> <schemaFiles>schema.xsd</schemaFiles> </configuration> </plugin> [/xml] Wenn Sie mehr über die Datenbindung mit JAXB erfahren möchten, finden Sie hier eine gut gefüllte Dokumentation.
Verfasst von

Yves AMSELLEM
Unsere Ideen
Weitere Blogs
Contact



