webentwicklung-frage-antwort-db.com.de

JAXB-Vererbung, entmarshal zur Unterklasse der gemarshallten Klasse

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.

42
Steve Kuo

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).

18
Pascal Thivent

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.

12
13ren

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();
    }
}
7
vocaro

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.

3
TofuBeer

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

0
Jason Law