Ich verwende JAXB zum Lesen und Schreiben von XML. Was ich möchte, ist die Verwendung einer Basis-JAXB-Klasse für das Marshalling und eine geerbte JAXB-Klasse für das Marshallen. Auf diese Weise können Sie einer Java-Senderanwendung ermöglichen, XML an eine andere Java-Empfängeranwendung zu senden. Sender und Empfänger teilen sich eine gemeinsame JAXB-Bibliothek. Ich möchte, dass der Empfänger das XML in eine empfängerspezifische JAXB-Klasse zerlegt, die die generische JAXB-Klasse erweitert.
Beispiel:
Dies ist die übliche JAXB-Klasse, die vom Sender verwendet wird.
@XmlRootElement(name="person")
public class Person {
public String name;
public int age;
}
Dies ist die empfängerspezifische JAXB-Klasse, die verwendet wird, wenn das XML entfernt wird. Die Empfängerklasse weist eine für die Empfängeranwendung spezifische Logik auf.
@XmlRootElement(name="person")
public class ReceiverPerson extends Person {
public doReceiverSpecificStuff() ...
}
Marshalling funktioniert wie erwartet. Das Problem liegt beim Unmarshalling, es wird jedoch nach JAXBContext unter Verwendung des Paketnamens der Unterklasse Person
noch ReceiverPerson
deklariert.
JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson);
Was ich will, ist ReceiverPerson
. Die einzige Möglichkeit, dies zu tun, ist, @XmlRootElement
von Person
zu entfernen. Leider wird dadurch verhindert, dass Person
gemarshallt wird. Es ist, als würde JAXB bei der Basisklasse beginnen und sich nach unten bewegen, bis der erste @XmlRootElement
mit dem entsprechenden Namen gefunden wird. Ich habe versucht, eine createPerson()
-Methode hinzuzufügen, die ReceiverPerson
an ObjectFactory
zurückgibt, aber das hilft nicht.
Sie verwenden JAXB 2.0 richtig? (seit JDK6)
Es gibt eine Klasse:
javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>
welche kann man subclass und folgende Methoden überschreiben:
public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;
Beispiel:
public class YourNiceAdapter
extends XmlAdapter<ReceiverPerson,Person>{
@Override public Person unmarshal(ReceiverPerson v){
return v;
}
@Override public ReceiverPerson marshal(Person v){
return new ReceiverPerson(v); // you must provide such c-tor
}
}
Die Nutzung erfolgt wie folgt:
@Your_favorite_JAXB_Annotations_Go_Here
class SomeClass{
@XmlJavaTypeAdapter(YourNiceAdapter.class)
Person hello; // field to unmarshal
}
Ich bin mir ziemlich sicher, dass Sie mit diesem Konzept den Marshalling/Unmarshalling-Prozess selbst steuern können (einschließlich der Auswahl des richtigen [sub | super] -Typs zum Konstruieren).
Der folgende Ausschnitt ist eine Methode eines Junit 4-Tests mit grünem Licht:
@Test
public void testUnmarshallFromParentToChild() throws JAXBException {
Person person = new Person();
int age = 30;
String name = "Foo";
person.name = name;
person.age= age;
// Marshalling
JAXBContext context = JAXBContext.newInstance(person.getClass());
Marshaller marshaller = context.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(person, writer);
String outString = writer.toString();
assertTrue(outString.contains("</person"));
// Unmarshalling
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader reader = new StringReader(outString);
RecieverPerson reciever = (RecieverPerson)unmarshaller.unmarshal(reader);
assertEquals(name, reciever.name);
assertEquals(age, reciever.age);
}
Der wichtige Teil ist die Verwendung der JAXBContext.newInstance(Class... classesToBeBound)
-Methode für den unmarshalling-Kontext:
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
Bei diesem Aufruf berechnet JAXB einen Referenzschluss für die angegebenen Klassen und erkennt RecieverPerson
. Der Test ist bestanden. Und wenn Sie die Reihenfolge der Parameter ändern, erhalten Sie einen Java.lang.ClassCastException
(so dass muss in dieser Reihenfolge übergeben werden muss).
Subclass Person zweimal, einmal für Empfänger und einmal für Sender, und nur das XmlRootElement in diese Unterklassen einfügen (die Superklasse Person
bleibt ohne XmlRootElement). Beachten Sie, dass sowohl Sender als auch Empfänger die gleichen JAXB-Basisklassen verwenden.
@XmlRootElement(name="person")
public class ReceiverPerson extends Person {
// receiver specific code
}
@XmlRootElement(name="person")
public class SenderPerson extends Person {
// sender specific code (if any)
}
// note: no @XmlRootElement here
public class Person {
// data model + jaxb annotations here
}
[getestet und für die Arbeit mit JAXB bestätigt]. Es umgeht das Problem, das Sie feststellen, wenn mehrere Klassen in der Vererbungshierarchie die XmlRootElement-Annotation haben.
Dies ist wahrscheinlich auch ein klarer und mehr OO - Ansatz, da er das gemeinsame Datenmodell herausfiltert und daher keine "Problemumgehung" darstellt.
Erstellen Sie eine benutzerdefinierte ObjectFactory, um die gewünschte Klasse während des Marshalls zu instanziieren. Beispiel:
JAXBContext context = JAXBContext.newInstance("com.whatever.mypackage");
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("com.Sun.xml.internal.bind.ObjectFactory", new ReceiverPersonObjectFactory());
return unmarshaller;
public class ReceiverPersonObjectFactory extends ObjectFactory {
public Person createPerson() {
return new ReceiverPerson();
}
}
Ich bin mir nicht sicher, warum Sie das tun wollen ... es scheint mir nicht so sicher zu sein.
Überlegen Sie sich, was in ReceiverPerson geschehen würde, hat zusätzliche Instanzvariablen ... dann würden Sie die Variablen mit Null, 0 oder False beenden (was wahrscheinlich ist) ?
Ich denke, was Sie wahrscheinlich tun möchten, ist in der Person zu lesen und dann eine neue ReceiverPerson daraus zu erstellen.
Der Schlüsselpunkt ist "Wenn das Marshalling abhängt, JAXB startet bei der Basisklasse und arbeitet nach unten", obwohl Sie ReceiverPerson
angeben, wenn Sie das Marshalling durchführen, Sie haben die Klasse Person
, die mit @XmlRootElement
kommentiert ist, so dass es zu Person
deklarieren wird.
Daher müssen Sie @XmlRootElement
in der Klasse Person
entfernen. Dadurch wird verhindert, dass Person
gemarshallt wird.
Die Lösung besteht darin, eine DummyPerson
zu erstellen, die Person erweitert und mit @XmlRootElement
kommentiert, dies macht DummyPerson
und ReceiverPerson
auf der gleichen Ebene. Dann können Sie DummyPerson
anstelle von Person
marshallieren und xmlString als ReceiverPerson
deklarieren.
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
public String name;
public int age;
}
@XmlRootElement(name = "person")
public class DummyPerson extends Person {
}
@XmlRootElement(name = "person")
public class ReceiverPerson extends Person {
public doReceiverSpecificStuff();
}
Verweise:
Vererbungsunterstützung in JAXB