Подтвердить что ты не робот

Наследование JAXB, не привязанное к подклассу маршалированного класса

Я использую JAXB для чтения и записи XML. Я хочу использовать базовый класс JAXB для сортировки и унаследованный класс JAXB для unmarshalling. Это позволяет 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() ...
}

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

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

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

4b9b3361

Ответ 1

Вы используете 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
}

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

Ответ 2

Следующий фрагмент - это метод теста 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) для unmarshalling context:

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

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

Ответ 3

Объект подкласса дважды, один раз для получателя и один раз для отправителя, и только помещаем 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.

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

Ответ 4

Создайте пользовательский объект ObjectFactory, чтобы создать экземпляр требуемого класса во время unmarshalling. Пример:

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

Ответ 5

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

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

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

Ответ 6

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