it-swarm.dev

JAXB dědictví, unmarshal k podtřídě zařazené třídy

Používám JAXB ke čtení a zápisu XML. Chtěl bych použít základní třídu JAXB pro zařazování a zděděnou třídu JAXB pro unmarshalling. To má umožnit odesílateli Java aplikace odesílat XML do jiné aplikace Java příjemce. Odesílatel a příjemce budou sdílet společnou knihovnu JAXB. Chci, aby přijímač unmarshall XML do přijímače specifické třídy JAXB, která rozšiřuje obecnou třídu JAXB.

Příklad:

Jedná se o běžnou třídu JAXB, kterou používá odesílatel.

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

Jedná se o třídu JAXB specifickou pro přijímač, která se používá při rozesílání XML. Třída přijímače má logiku specifickou pro aplikaci přijímače.

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

Marshalling funguje podle očekávání. Problém je s unmarshalling, to ještě unmarshals k Person přes JAXBContext používat jméno balíčku subclassed ReceiverPerson.

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

To, co chci, je unmarshall k ReceiverPerson. Jediný způsob, jak to udělat, je odstranit @XmlRootElement z Person. Bohužel to zabraňuje tomu, aby Person bylo zařazeno. Je to, jako by JAXB začínal na základní třídě a postupoval dolů, dokud nenajde první @XmlRootElement s příslušným názvem. Snažil jsem se přidat metodu createPerson(), která vrací ReceiverPerson do ObjectFactory, ale to nepomůže.

42
Steve Kuo

Používáte JAXB 2.0 správně? (od JDK6)

Existuje třída:

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

které lze podtřídy a přepsat následující metody:

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

Příklad:

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

Použití se provádí následujícím způsobem:

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

Jsem si jistý, že pomocí tohoto konceptu můžete ovládat proces zařazování/odposlouchávání sami (včetně volby správného typu [sub | super] konstrukce).

20

Následující úryvek je metoda testu Junit 4 se zeleným světlem:

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

Důležitou součástí je použití metody JAXBContext.newInstance(Class... classesToBeBound) pro kontext unmarshalling:

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

S tímto voláním vypočítá JAXB referenční uzavření na určených třídách a rozpozná RecieverPerson. Test prošel. A pokud změníte pořadí parametrů, dostanete Java.lang.ClassCastException (takže musí být předány v tomto pořadí).

18
Pascal Thivent

Podtřída Osoba dvakrát, jednou pro příjemce a jednou pro odesílatele, a pouze umístit XmlRootElement na tyto subclassses (opouštět nadtřídu, Person, bez XmlRootElement). Všimněte si, že odesílatel i přijímač sdílí stejné základní třídy 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
}

[testováno a potvrzeno, že bude pracovat s JAXB]. Obchází problém, který si všimnete, když více tříd v hierarchii dědičnosti obsahuje anotaci XmlRootElement.

To je pravděpodobně také neater a více OO přístup, protože se odděluje společný datový model, takže to není "řešení" vůbec.

12
13ren

Vytvořit vlastní ObjectFactory vytvořit instanci požadované třídy během unmarshalling. Příklad:

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

Nejsem si jistý, proč bys to chtěl udělat ... nezdá se mi to tak bezpečné.

Zvažte, co by se stalo v aplikaci ReceiverPerson má další proměnné instancí ... pak byste skončili s (myslím), že tyto proměnné jsou null, 0 nebo false ... a co když null není povoleno nebo číslo musí být větší než 0 ?

Myslím, že to, co pravděpodobně budete chtít udělat, je číst v Osobě a pak z ní vytvořit nový ReceiverPerson (pravděpodobně poskytnete konstruktor, který vezme Osobu).

3
TofuBeer

Klíčovým bodem je, "když unmarshalling, JAXB začíná na základní třídě a pracuje na cestě dolů", i když zadáte ReceiverPerson když unmarshalling, máte třídu Person anotován @XmlRootElement, takže to bude unmarshal Person.

Takže je třeba odstranit @XmlRootElement ve třídě Person, ale to zabrání tomu, aby Person bylo zařazeno.

Řešením je vytvořit DummyPerson, která rozšiřuje Osobu a anotuje ji @XmlRootElement, což dělá DummyPerson a ReceiverPerson na stejné úrovni, pak můžete namísto DummyPerson a unmarshal xmlString na Person namísto 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();
}

Reference:
Podpora dědičnosti v JAXB

0
Jason Law