web-dev-qa-db-fra.com

Héritage JAXB, unmarshal à la sous-classe de la classe marshaled

J'utilise JAXB pour lire et écrire du XML. Ce que je veux, c'est utiliser une classe JAXB de base pour le marshalling et une classe JAXB héritée pour unmarshalling. Cela permet à une application Java émettrice d'envoyer du XML à une autre application Java réceptrice. L'expéditeur et le destinataire partagent une bibliothèque JAXB commune. Je souhaite que le destinataire libère le code XML en une classe JAXB spécifique au récepteur, qui étend la classe JAXB générique.

Exemple:

Il s'agit de la classe JAXB commune utilisée par l'expéditeur.

@XmlRootElement(name="person")
public class Person {
    public String name;
    public int age;
}

Il s'agit de la classe JAXB spécifique au récepteur utilisée pour disséminer le XML. La classe de récepteur a une logique spécifique à l'application de récepteur.

@XmlRootElement(name="person")
public class ReceiverPerson extends Person {
    public doReceiverSpecificStuff() ...
}

Le Marshalling fonctionne comme prévu. Le problème réside dans la suppression de la fusion, il reste toujours dans la variable Person malgré le contexte JAXBContext utilisant le nom de package de la sous-classe ReceiverPerson.

JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson);

Ce que je veux, c’est d’annuler ReceiverPerson. La seule façon dont j'ai pu faire cela est de supprimer @XmlRootElement de Person. Malheureusement, cela empêche Person d'être marshalé. C'est comme si JAXB commençait à la classe de base et descendait jusqu'à trouver le premier @XmlRootElement avec le nom approprié. J'ai essayé d'ajouter une méthode createPerson() qui renvoie ReceiverPerson à ObjectFactory mais cela n'aide pas.

42
Steve Kuo

Vous utilisez JAXB 2.0, n'est-ce pas? (depuis JDK6)

Il y a une classe:

javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>

lequel on peut sous-classer, et écraser les méthodes suivantes:

public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;

Exemple:

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
    }
}

L'utilisation se fait comme suit:

@Your_favorite_JAXB_Annotations_Go_Here
class SomeClass{
    @XmlJavaTypeAdapter(YourNiceAdapter.class)
    Person hello; // field to unmarshal
}

Je suis presque sûr qu'en utilisant ce concept, vous pouvez contrôler vous-même le processus de marshalling/unmarshalling (y compris le choix du type [sub | super] correct à construire).

20

L'extrait suivant est une méthode de test Junit 4 avec une lumière verte:

@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);
}

La partie importante est l’utilisation de la méthode JAXBContext.newInstance(Class... classesToBeBound) pour le contexte unmarshalling:

 context = JAXBContext.newInstance(Person.class, RecieverPerson.class);

Avec cet appel, JAXB calculera une fermeture de référence sur les classes spécifiées et reconnaîtra RecieverPerson. Le test est réussi. Et si vous changez l'ordre des paramètres, vous obtiendrez un Java.lang.ClassCastException (donc ils doivent être passés dans cet ordre).

18
Pascal Thivent

Sous-classe Person deux fois, une pour le destinataire et une autre pour l'expéditeur, et placez uniquement XmlRootElement sur ces sous-classes (en laissant la super-classe, Person, sans XmlRootElement). Notez que l'expéditeur et le destinataire partagent les mêmes classes de base JAXB.

@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
}

[testé et confirmé pour fonctionner avec JAXB]. Cela résout le problème que vous avez noté lorsque plusieurs classes de la hiérarchie d'héritage ont l'annotation XmlRootElement.

C'est sans doute aussi une approche plus soignée et plus OO, car elle sépare le modèle de données commun, de sorte que ce n'est pas du tout une "solution de contournement".

12
13ren

Créez un ObjectFactory personnalisé pour instancier la classe souhaitée lors du démultiplication. Exemple:

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

Je ne sais pas pourquoi vous voudriez faire ça ... ça ne me semble pas si sûr.

Réfléchissez à ce qui se produirait dans ReceiverPerson si vous avez des variables d’instance supplémentaires ... et vous finissez avec (je suppose) que ces variables sont nulles, nulles ou fausses ... et que faire si null n’est pas autorisé ou si le nombre doit être supérieur à 0 ?

Je pense que ce que vous voulez probablement faire est de lire dans la personne, puis de construire un nouveau ReceiverPerson à partir de celle-ci (fournissez probablement un constructeur prenant une personne).

3
TofuBeer

Le point clé est "lorsqu’un remplissage est effectué, JAXB commence à la classe de base et continue à descendre", bien que vous spécifiiez ReceiverPerson lorsqu’il est masqué, vous avez la classe Person annotée avec @XmlRootElement, de sorte qu’elle restaure à Person.

Donc, vous devez supprimer @XmlRootElement dans la classe Person, mais ceci évite que Person soit marshalé.

La solution consiste à créer une variable DummyPerson qui étend la personne et à l'annoter avec @XmlRootElement. Ceci rend DummyPerson et ReceiverPerson au même niveau, vous pouvez alors marshaller DummyPerson au lieu de Person et unmarshal xmlString to ReceiverPerson.

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

Références:
Prise en charge des héritages dans JAXB

0
Jason Law