Зачем JAXB нужен конструктор без аргументов для сортировки? - PullRequest
43 голосов
/ 14 февраля 2012

Если вы попытаетесь упорядочить класс, который ссылается на сложный тип, у которого нет конструктора без аргументов, например:

import java.sql.Date;

@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
    int i;
    Date d; //java.sql.Date does not have a no-arg constructor
}

с реализацией JAXB, являющейся частью Java, следующим образом:

    Foo foo = new Foo();
    JAXBContext jc = JAXBContext.newInstance(Foo.class);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Marshaller marshaller = jc.createMarshaller();
    marshaller.marshal(foo, baos);

JAXB бросит

com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions java.sql.Date does not have a no-arg default constructor

Теперь я понимаю, почему JAXB нужен конструктор без аргументов при демаршаллинге - потому что ему нужно создать экземпляр объекта. Но зачем JAXB нужен конструктор без аргументов при маршалинге?

Кроме того, еще один вопрос: почему реализация JAXB в Java выдает исключение, если поле имеет значение null и не собирается в любом случае маршалировать?

Я что-то упустил или это просто неудачный выбор реализации в реализации JAXB Java?

Ответы [ 4 ]

24 голосов
/ 14 февраля 2012

Когда реализация JAXB (JSR-222) инициализирует свои метаданные, она гарантирует, что она может поддерживать как маршалинг, так и демаршаллинг.

Для классов POJO, которые не имеют конструктора без аргументов, вы можете использовать уровень типа XmlAdapter для его обработки:

java.sql.Date не поддерживается по умолчанию (хотя в EclipseLink JAXB (MOXy) это так).Это также может быть обработано с помощью XmlAdapter, заданного через @XmlJavaTypeAdapter на уровне поля, свойства или пакета:

Кроме того, еще один вопрос: почему реализация JAXB в Java выдает исключение, если поле имеет значение null и не собирается в любом случае маршализоваться?

Какое исключение вы видите?Обычно, когда поле имеет значение NULL, оно не включается в результат XML, если только оно не помечено @XmlElement(nillable=true), в этом случае элемент будет включать xsi:nil="true".

ОБНОВЛЕНИЕ

Вы можете сделать следующее:

SqlDateAdapter

Ниже приведено XmlAdapter, которое преобразуетот java.sql.Date, который ваша реализация JAXB не знает, как обрабатывать до java.util.Date, который он делает:

package forum9268074;

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

public class SqlDateAdapter extends XmlAdapter<java.util.Date, java.sql.Date> {

    @Override
    public java.util.Date marshal(java.sql.Date sqlDate) throws Exception {
        if(null == sqlDate) {
            return null;
        }
        return new java.util.Date(sqlDate.getTime());
    }

    @Override
    public java.sql.Date unmarshal(java.util.Date utilDate) throws Exception {
        if(null == utilDate) {
            return null;
        }
        return new java.sql.Date(utilDate.getTime());
    }

}

Foo

XmlAdapter зарегистрирован через аннотацию @XmlJavaTypeAdapter:

package forum9268074;

import java.sql.Date;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
    int i;

    @XmlJavaTypeAdapter(SqlDateAdapter.class)
    Date d; //java.sql.Date does not have a no-arg constructor
}
3 голосов
/ 27 октября 2018

Чтобы ответить на ваш вопрос: я думаю, что это просто плохой дизайн в JAXB (или, возможно, в реализациях JAXB).Существование конструктора без аргументов проверяется при создании JAXBContext и поэтому применяется независимо от того, хотите ли вы использовать JAXB для маршалинга или демаршаллинга.Было бы здорово, если бы JAXB отложил этот тип проверки до JAXBContext.createUnmarshaller().Я думаю, что было бы интересно разобраться, действительно ли этот дизайн предписан спецификацией или это проект реализации в JAXB-RI.

Но действительно есть обходной путь.

JAXB не делаетна самом деле не нужен конструктор без аргументов для сортировки.Далее я предполагаю, что вы используете JAXB исключительно для маршалинга, а не демаршаллинга.Я также предполагаю, что у вас есть контроль над неизменным объектом, который вы хотите маршалировать, чтобы вы могли его изменить.Если это не так, то единственный путь вперед - это XmlAdapter, как описано в других ответах.

Предположим, у вас есть класс Customer, который является неизменным объектом.Инстанцирование происходит через Pattern Builder или статические методы.

public class Customer {

    private final String firstName;
    private final String lastName;

    private Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Object created via builder pattern
    public static CustomerBuilder createBuilder() {
        ...
    }

    // getters here ...
}

Правда, по умолчанию вы не можете заставить JAXB разобрать такой объект.Вы получите ошибку " .... У клиента нет конструктора по умолчанию без аргументов ".

Есть как минимум два способа решения этой проблемы.Они оба используют метод или конструктор исключительно для того, чтобы сделать интроспекцию JAXB счастливой.

Решение 1

В этом методе мы сообщаем JAXB, что существует статический метод фабрики, который он может использовать для создания экземпляра класса.Мы знаем, но JAXB не знает, что это никогда не будет использовано.Хитрость заключается в аннотации @XmlType с параметром factoryMethod.Вот как это делается:

@XmlType(factoryMethod="createInstanceJAXB")
public class Customer {
    ...

    private static CustomerImpl createInstanceJAXB() {  // makes JAXB happy, will never be invoked
        return null;  // ...therefore it doesn't matter what it returns
    }

    ...
}

Не имеет значения, является ли метод закрытым, как в примере.JAXB все еще примет это.Ваша IDE пометит метод как неиспользуемый, если вы сделаете его закрытым, но я все еще предпочитаю частный.

Решение 2

В этом решении мы добавляем приватный no-argконструктор, который просто передает null в реальный конструктор.

public class Customer {
    ...
    private Customer() {  // makes JAXB happy, will never be invoked
        this(null, null);   // ...therefore it doesn't matter what it creates
    }

    ...
}

Не имеет значения, является ли конструктор закрытым, как в примере.JAXB все еще примет это.

Резюме

Оба решения удовлетворяют стремление JAXB к созданию экземпляров без аргументов.Обидно, что нам нужно это делать, когда мы сами знаем, что нам нужно только маршалировать, а не демаршалировать.

Я должен признать, что не знаю, в какой степени это хак, который будетработать только с JAXB-RI, а не, например, с EclipseLink MOXy.Это определенно работает с JAXB-RI.

0 голосов
/ 14 февраля 2012

Похоже, у вас сложилось впечатление, что код интроспекции JAXB будет содержать специфические для действий пути для инициализации. если это так, это приведет к большому количеству дублирующегося кода и будет плохой реализацией. я бы предположил, что код JAXB имеет общую подпрограмму, которая проверяет класс модели в первый раз, когда это необходимо, и проверяет, что он следует всем необходимым соглашениям. в этой ситуации происходит сбой, поскольку у одного из участников нет необходимого конструктора без аргументов. логика инициализации, скорее всего, не является специфичной для маршала / демаршала, а также крайне маловероятно, чтобы учитывать текущий экземпляр объекта.

0 голосов
/ 14 февраля 2012

В некоторых корпоративных и инфраструктурах внедрения зависимостей используется отражение Class.newInstance () для создания нового экземпляра ваших классов. Этот метод требует, чтобы общедоступный конструктор без аргументов мог создать экземпляр объекта.

...