Наследование JAXB, не маршальное для подкласса маршалированного класса - PullRequest
42 голосов
/ 06 марта 2009

Я использую JAXB для чтения и записи XML. Я хочу использовать базовый класс JAXB для маршалинга и унаследованный класс JAXB для демаршаллинга. Это позволяет отправляющему Java-приложению отправлять XML другому получающему Java-приложению. Отправитель и получатель будут использовать общую библиотеку JAXB. Я хочу, чтобы получатель аннулировал XML в специальный класс JAXB получателя, который расширяет общий класс JAXB.

Пример:

Это общий класс JAXB, который используется отправителем.

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

Это специфичный для получателя класс JAXB, используемый при демаршаллинге XML. Класс получателя имеет логику, специфичную для приложения получателя.

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

Маршаллинг работает как положено. Проблема с демаршалингом, он все еще демаршализируется до Person несмотря на то, что JAXBContext использует имя пакета для подкласса ReceiverPerson.

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

Чего я хочу, так это разобраться с ReceiverPerson. Единственный способ сделать это - удалить @XmlRootElement из Person. К сожалению, это не позволяет маршалировать Person. Это как если бы JAXB начинал с базового класса и продолжал свой путь вниз, пока не нашел первый @XmlRootElement с соответствующим именем. Я попытался добавить метод createPerson(), который возвращает ReceiverPerson в ObjectFactory, но это не помогает.

Ответы [ 7 ]

20 голосов
/ 12 марта 2009

Вы используете JAXB 2.0, верно? (начиная с JDK6)

Есть класс:

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

какой из них может быть подклассом, и переопределить следующие методы:

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

Пример:

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

Использование осуществляется следующим образом:

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

Я уверен, что с помощью этой концепции вы можете самостоятельно управлять процессом маршаллинга / демаршаллинга (включая выбор правильного типа [sub | super] для построения).

19 голосов
/ 18 марта 2009

Следующий фрагмент является методом теста Junit 4 с зеленым светом:

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

Важной частью является использование метода JAXBContext.newInstance(Class... classesToBeBound) для демаршаллинга:

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

С помощью этого вызова JAXB вычислит закрытие ссылки для указанных классов и распознает RecieverPerson. Тест проходит. И если вы измените порядок параметров, вы получите java.lang.ClassCastException (поэтому они должны передаваться в этом порядке).

12 голосов
/ 18 марта 2009

Подкласс Person дважды, один раз для получателя и один раз для отправителя, и помещает XmlRootElement только в эти подклассы (оставляя суперкласс Person без XmlRootElement). Обратите внимание, что отправитель и получатель используют одни и те же базовые классы 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
}

[проверено и подтверждено для работы с JAXB]. Это позволяет обойти проблему, которую вы заметили, когда несколько классов в иерархии наследования имеют аннотацию XmlRootElement.

Возможно, это также более аккуратный и более ОО-подход, поскольку он отделяет общую модель данных, поэтому это вовсе не «обходной путь».

7 голосов
/ 01 декабря 2010

Создайте пользовательскую ObjectFactory для создания экземпляра нужного класса во время демаршаллинга. Пример:

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();
    }
}
3 голосов
/ 06 марта 2009

Я не уверен, почему вы захотите сделать это ... мне это не кажется безопасным.

Рассмотрим, что произойдет в ReceiverPerson с дополнительными переменными экземпляра ... тогда вы получите (я полагаю), что эти переменные имеют значение null, 0 или false ... и что, если null не разрешен или число должно быть больше 0?

Я думаю, что вы, вероятно, хотите сделать, это прочитать в Person, а затем создать из него новый ReceiverPerson (вероятно, предоставьте конструктор, который принимает Person).

0 голосов
/ 07 января 2019

Ключевым моментом является то, что «при демаршаллинге JAXB начинается с базового класса и работает в нисходящем направлении», хотя вы указываете ReceiverPerson при демаршаллинге, у вас есть класс Person с аннотацией @XmlRootElement, поэтому он будет отменен Person.

Таким образом, вам нужно удалить @XmlRootElement в классе Person, но это не позволит маршалировать Person.

Решение состоит в том, чтобы создать DummyPerson, который расширяет Person и аннотирует его с помощью @XmlRootElement, это делает DummyPerson и ReceiverPerson на том же уровне , тогда вы можете маршала DummyPerson вместо Person и unmarshal xmlString для 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();
}

Ссылки:
Поддержка наследования в JAXB

0 голосов
/ 17 марта 2009

Поскольку у вас действительно есть два отдельных приложения, скомпилируйте их с разными версиями класса "Person" - при этом приложение-получатель не имеет @XmlRootElement(name="person") на Person. Это не только уродливо, но и отрицательно сказывается на удобстве сопровождения, которое вы хотели использовать, используя одно и то же определение Person для отправителя и получателя. Его единственная особенность - это то, что он работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...