Хобрук: Ваш путь к мастерству в программировании

Почему ObjectFactory не используется во время десортировки?

Я определил следующие ObjectFactory:

@XmlRegistry
public class ObjectFactory {

    public Dogs createDogs() {
        return new Dogs();
    }

    @XmlElementDecl(name = "dog")
    public Dog createDog(DogType value) {
        return new Dog(value);
    }

    @XmlElementDecl(name = "fido", substitutionHeadName = "dog", substitutionHeadNamespace = "")
    public Dog createFido(DogType value) {
        return new Dog("fido", value);
    }

    @XmlElementDecl(name = "barks", substitutionHeadName = "dog", substitutionHeadNamespace = "")
    public Dog createBarks(DogType value) {
        return new Dog("barks", value);
    }
}

(класс Dogs тривиален, Dog и DogType см. ниже или здесь.)

Я распаковываю следующий XML:

<listOfDogs>
    <dogs>
        <dog>
            <name>henry</name>
            <sound>bark</sound>
        </dog>
        <fido>
            <sound>woof</sound>
        </fido>
        <barks>
            <sound>miau</sound>
        </barks>
    </dogs>
</listOfDogs>

Я искренне ожидал, что JAXB вызовет мои методы createFido(...) и createBarks(...) во время десортировки. Но этого не происходит. Конструктор Dog вызывается напрямую через отражение, соответствующие методы create... не используются.

Мой вопрос:

Почему ObjectFactory не вызывается во время разупорядочения?

Разве это не должно быть? Или это просто манекен для хранения объявлений @XmlRegistry/@XmlElementDecl?

Я также проверил этот вопрос:

Какова роль ObjectFactory во время JAXB-Unmarshalling?

Решение заключается в использовании @XmlType.factoryClass и factoryMethod. Здесь это не сработает, потому что я не хочу статически связывать свой DogType с определенной процедурой создания экземпляра. Я хочу, чтобы это решалось во время выполнения на основе имени элемента. Моя цель — создать экземпляр одного и того же класса, но по-разному, в зависимости от имени элемента.


Теперь немного кода, чтобы завершить его.

Класс корневого элемента:

@XmlRootElement(name = "listOfDogs")
public class Dogs {

    private List<JAXBElement<DogType>> dogs = new LinkedList<JAXBElement<DogType>>();

    @XmlElementWrapper(name = "dogs")
    @XmlElementRef(name = "dog")
    public List<JAXBElement<DogType>> getDogs() {
        return this.dogs;
    }

    @Override
    public String toString() {
        return "Dogs [dogs=" + dogs + "]";
    }
}

Dog, класс элемента-оболочки для DogType:

public class Dog extends JAXBElement<DogType> {

    public static final QName NAME = new QName("dog");

    private static final long serialVersionUID = 1L;

    public Dog(DogType value) {
        super(NAME, DogType.class, value);
    }

    public Dog(String dogName, DogType value) {
        super(NAME, DogType.class, value);
    }

    @Override
    public QName getName() {
        final DogType value = getValue();
        if (value != null && value.getName() != null) {
            return new QName(value.getName());
        } else {
            return super.getName();
        }
    }
}

DogType:

public class DogType {

    private String name;
    private String sound;

    public String getName() {
        return name;
    }

    public void setName(String dogName) {
        this.name = dogName;
    }

    public String getSound() {
        return sound;
    }

    public void setSound(String sound) {
        this.sound = sound;
    }
}

Контрольная работа:

public class DogTest {

    @Test
    public void unmarshallsDogs() throws JAXBException {
        final JAXBContext context = JAXBContext
                .newInstance(ObjectFactory.class);
        final Dogs dogs = (Dogs) context.createUnmarshaller().unmarshal(
                getClass().getResource("dogs.xml"));
        Assert.assertEquals(3, dogs.getDogs().size());
        // Does not work
//      Assert.assertEquals("henry", dogs.getDogs().get(0).getValue()
//              .getName());
        Assert.assertEquals("bark", dogs.getDogs().get(0).getValue().getSound());
        // Does not work
//      Assert.assertEquals("fido", dogs.getDogs().get(1).getValue()
//              .getName());
        Assert.assertEquals("woof", dogs.getDogs().get(1).getValue().getSound());
        // Does not work
//      Assert.assertEquals("barks", dogs.getDogs().get(2).getValue()
//              .getName());
        Assert.assertEquals("miau", dogs.getDogs().get(2).getValue().getSound());
    }
}

Код также доступен на GitHub здесь и здесь.

18.10.2014

Ответы:


1

Краткий ответ заключается в том, что фабричные методы не генерируются в аннотации @XmlType, чтобы указать JAXB сделать это:

@XmlRootElement(name = "listOfDogs")
@XmlType(factoryClass=ObjectFactory.class, factoryMethod="createDogs") // not generated
public class Dogs {

Разве это не должно быть? Или это просто манекен для хранения объявлений @XmlRegistry/@XmlElementDecl?

На мой взгляд, да, его следует использовать для создания экземпляров классов.

ObjectFactory — это возврат к JAXB 1.0. В JAXB 1.0 спецификация определяла, как выглядели сгенерированные интерфейсы, и реализации могли поддерживать эти сгенерированные интерфейсы с тем, что они хотели предоставить. Тогда вам нужно было использовать класс ObjectFactory для создания вашей модели независимым от поставщика способом.

JAXB 2.0 переключился на модель POJO, где вы могли свободно использовать конструктор по умолчанию. Если бы JAXB 1.0 никогда не существовало, был бы класс ObjectFactory, трудно сказать. Поскольку он существовал ранее, класс ObjectFactory был сохранен по нескольким причинам:

  1. Людям, переходящим с JAXB 1.0, стало проще взаимодействовать с созданной моделью.
  2. Он предоставил место для указания нескольких корневых элементов для класса через @XmlElementDecl. Аннотация @XmlRegistry на самом деле является просто маркерной аннотацией, используемой для обозначения класса, содержащего аннотации @XmlElementDecl, не ограничивая его классом с именем ObjectFactory.

Ваш вариант использования

Ваш вариант использования может быть достигнут с помощью XmlAdapter, хотя мне не ясно, какую логику вы пытаетесь использовать в ObjectFactory.

XmlAdapter (собачий адаптер)

Ваша пользовательская логика идет на XmlAdapter.

import javax.xml.bind.*;
import javax.xml.bind.annotation.adapters.*;

public class DogAdapter extends XmlAdapter<JAXBElement<DogType>, JAXBElement<DogType>> {

    @Override
    public JAXBElement<DogType> unmarshal(JAXBElement<DogType> v) throws Exception {
        return new Dog(v.getName().getLocalPart(), v.getValue());
    }

    @Override
    public JAXBElement<DogType> marshal(JAXBElement<DogType> v) throws Exception {
        return v;
    }

}

Собаки

XmlAdapter упоминается в аннотации @XmlJavaTypeAdapter.

import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name = "listOfDogs")
public class Dogs {

    private List<JAXBElement<DogType>> dogs = new LinkedList<JAXBElement<DogType>>();

    @XmlElementWrapper(name = "dogs")
    @XmlElementRef(name = "dog")
    @XmlJavaTypeAdapter(DogAdapter.class)
    public List<JAXBElement<DogType>> getDogs() {
        return this.dogs;
    }

    @Override
    public String toString() {
        return "Dogs [dogs=" + dogs + "]";
    }

}

Фабрика объектов

ObjectFactory теперь является тупым классом, который просто содержит аннотации @XmlElementDecl:

import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;

@XmlRegistry
public class ObjectFactory {

    public Dogs createDogs() {
        return new Dogs();
    }

    @XmlElementDecl(name = "dog")
    public JAXBElement<DogType> createDog(DogType value) {
        return new Dog(value);
    }

    @XmlElementDecl(name = "fido", substitutionHeadName = "dog", substitutionHeadNamespace = "")
    public JAXBElement<DogType> createFido(DogType value) {
        return new JAXBElement<DogType>(new QName("fido"), DogType.class, value);
    }

    @XmlElementDecl(name = "barks", substitutionHeadName = "dog", substitutionHeadNamespace = "")
    public JAXBElement<DogType> createBarks(DogType value) {
        return new JAXBElement<DogType>(new QName("barks"), DogType.class, value);
    }

}

ОБНОВИТЬ

Мой вопрос, однако, больше о спецификации. Согласно спецификации должны выполняться методы create* из ObjectFactory или нет?

В JAXB 2 нет разницы между моделью, созданной с нуля, и моделью, сгенерированной из XML-схемы. Таким образом, вам нужно посмотреть спецификацию на то, что она говорит о классах. Согласно приведенной ниже ссылке, речь идет о конструкторе без аргументов или указанном фабричном методе.

Из раздела 8.7.1.2 Сопоставление документа JAXB 2.2 (JSR-222) спецификация:

класс должен иметь общедоступный или защищенный конструктор без аргументов или фабричный метод, идентифицируемый {factoryClass(), factoryMethod()}, если только он не адаптирован с использованием @XmlJavaTypeAdapter.

07.11.2014
  • Итак, ObjectFactory — это просто манекен для хранения объявлений @XmlRegistry/@XmlElementDecl? Не кажется мне правильным. 07.11.2014
  • @lexicore - я расширяю свой ответ, чтобы объяснить больше, но это может занять некоторое время. 07.11.2014
  • @lexicore - Какую логику вы пытаетесь внедрить в свой ObjectFactory класс? 07.11.2014
  • Передача различных параметров конструктору экземпляра в зависимости от имени корневого элемента. Пожалуйста, проверьте методы createDog, createFido и createBarks в моем примере. Они создают один и тот же экземпляр, но передают конструктору разные имена. 07.11.2014
  • Вот почему я не могу использовать @XmlType с factoryClass и factoryMethod - я хочу использовать один и тот же класс для разных корневых элементов. 07.11.2014
  • @lexicore - я добавил XmlAdapter пример, который должен дать вам поведение, эквивалентное тому, что вы ищете. 07.11.2014
  • Спасибо за ваши усилия, Блейз. Мой вопрос, однако, больше о спецификации. Согласно спецификации должны ли выполняться create* методы из ObjectFactory? Если они должны, то в JAXB RI есть ошибка. Если нет, вы предоставили очень хороший обходной путь. Итак, практически проблема решена, но мне также интересно разобраться в спецификации. 07.11.2014
  • @lexicore — в спецификации указан конструктор по умолчанию или указанный фабричный метод (см. обновление). 08.11.2014
  • Извините, забыл наградить награду. 13.11.2014
  • Новые материалы

    Пример O (n²)
    Две суммы: найти все пары, сумма которых равна целевому значению const arr = [1,4,5,7,9,3,1,2,-4,-6] Целевое значение = 9 Использование Brute Force для этого случая будет выполняться с..

    Правильное тестирование машинного обучения: обнаружение ошибок в данных.
    Эта статья была первоначально размещена на нашем сайте компании . Платформа для разработчиков Lakera позволяет командам машинного обучения создавать отказоустойчивые модели компьютерного..

    Обучение SAP FICO в NOIDA.
    Лучший провайдер SAP Training с целевым размещением SAP-коучинга в Нойде. Наш курс SAP концентрируется от коучинга базового уровня до продвинутого уровня и охватывает как функциональные, так и..

    Sinkhorn Knopp: поиск оптимального транспорта для выравнивания данных
    В области выравнивания данных и оптимального транспорта алгоритм Синкхорна-Кноппа стал мощным инструментом для решения задач оптимизации транспорта. С приложениями, варьирующимися от сопоставления..

    Мой начальный путь к микросервисам с Spring Boot
    Почему мы используем микросервисы? Микросервисы — это шаблон проектирования программного обеспечения, который включает создание большого приложения в виде набора небольших независимых..

    Уменьшите количество шаблонов при запуске тестов Kotlin
    Используя изящный трюк Kotlin, вы можете сделать свои тесты чистыми и простыми для понимания и обслуживания. Тестирование должно быть легким. Если ваши тесты слишком сложны и сложны в..

    Понимание React.js: гармоничная симфония компонентов и модульность в стиле LEGO
    Понимание React.js: гармоничная симфония компонентов и модульность в стиле LEGO Представляем искусство и науку, лежащую в основе строительных блоков React.js, React Components, которые помогают..