«1. Введение

В этом уроке мы увидим, как демаршалировать объекты даты в разных форматах с помощью JAXB.

Сначала мы рассмотрим формат даты схемы по умолчанию. Затем мы рассмотрим, как использовать различные форматы. Мы также увидим, как мы можем справиться с общей проблемой, возникающей с помощью этих методов.

2. Привязка схемы к Java

Во-первых, нам нужно понять взаимосвязь между XML-схемой и типами данных Java. В частности, нас интересует сопоставление между XML-схемой и объектами даты Java.

Согласно сопоставлению схемы с Java, необходимо учитывать три типа данных схемы: xsd:date, xsd:time и xsd:dateTime. Как мы видим, все они сопоставлены с javax.xml.datatype.XMLGregorianCalendar.

Нам также необходимо понять форматы по умолчанию для этих типов схемы XML. Типы данных xsd:date и xsd:time имеют форматы «ГГГГ-ММ-ДД» и «чч:мм:сс». Формат xsd:dateTime — «ГГГГ-ММ-ДДTчч:мм:сс», где «T» — разделитель, указывающий начало временного раздела.

3. Использование формата даты схемы по умолчанию

Мы собираемся создать пример, который неупорядочивает объекты даты. Давайте сосредоточимся на типе данных xsd:dateTime, так как он представляет собой надмножество других типов.

Давайте воспользуемся простым XML-файлом, описывающим книгу:

<book>
    <title>Book1</title>
    <published>1979-10-21T03:31:12</published>
</book>

Мы хотим сопоставить файл с соответствующим объектом Java Book:

@XmlRootElement(name = "book")
public class Book {

    @XmlElement(name = "title", required = true)
    private String title;

    @XmlElement(name = "published", required = true)
    private XMLGregorianCalendar published;

    @Override
    public String toString() {
        return "[title: " + title + "; published: " + published.toString() + "]";
    }

}

Наконец, нам нужно создать клиентское приложение, которое преобразует данные XML в объекты Java, производные от JAXB:

public static Book unmarshalDates(InputStream inputFile) 
  throws JAXBException {
    JAXBContext jaxbContext = JAXBContext.newInstance(Book.class);
    Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
    return (Book) jaxbUnmarshaller.unmarshal(inputFile);
}

В приведенном выше коде мы определили JAXBContext, который является точкой входа в JAXB API. Затем мы использовали JAXB Unmarshaller во входном потоке, чтобы прочитать наш объект:

Если мы запустим приведенный выше код и напечатаем результат, мы получим следующий объект Book:

[title: Book1; published: 1979-11-28T02:31:32]

Мы следует отметить, что, несмотря на то, что сопоставлением по умолчанию для xsd:dateTime является XMLGregorianCalendar, мы также могли бы использовать более распространенные типы Java: java.util.Date и java.util.Calendar, согласно руководству пользователя JAXB.

4. Использование пользовательского формата даты

Приведенный выше пример работает, потому что мы используем формат даты схемы по умолчанию, «ГГГГ-ММ-ДДTчч:мм:сс».

Но что, если мы хотим использовать другой формат, например «ГГГГ-ММ-ДД чч:мм:сс», избавившись от разделителя «Т»? Если бы мы заменили разделитель символом пробела в нашем XML-файле, неупорядочивание по умолчанию завершилось бы неудачей.

4.1. Создание пользовательского XmlAdapter

Чтобы использовать другой формат даты, нам нужно определить XmlAdapter.

Давайте также посмотрим, как сопоставить тип xsd:dateTime с объектом java.util.Date с помощью нашего пользовательского XmlAdapter:

public class DateAdapter extends XmlAdapter<String, Date> {

    private static final String CUSTOM_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss";

    @Override
    public String marshal(Date v) {
        return new SimpleDateFormat(CUSTOM_FORMAT_STRING).format(v);
    }

    @Override
    public Date unmarshal(String v) throws ParseException {
        return new SimpleDateFormat(CUSTOM_FORMAT_STRING).parse(v);
    }

}

В этом адаптере мы использовали SimpleDateFormat для форматирования даты. Нам нужно быть осторожными, так как SimpleDateFormat не является потокобезопасным. Чтобы избежать проблем с общим объектом SimpleDateFormat в нескольких потоках, мы создаем новый каждый раз, когда он нам нужен.

4.2. Внутреннее устройство XmlAdapter

Как мы видим, XmlAdapter имеет два параметра типа, в данном случае String и Date. Первый тип используется внутри XML и называется типом значения. В этом случае JAXB знает, как преобразовать значение XML в строку. Второй тип называется связанным типом и относится к значению в нашем объекте Java.

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

Чтобы создать собственный XmlAdapter, нам нужно переопределить два метода: XmlAdapter.marshal() и XmlAdapter.unmarshal().

Во время демаршалинга структура привязки JAXB сначала демаршалирует представление XML в String, а затем вызывает DateAdapter.unmarshal() для адаптации типа значения к Date. Во время сортировки структура привязки JAXB вызывает DateAdapter.marshal() для адаптации даты к строке, которая затем маршалируется в представление XML.

4.3. Интеграция через аннотации JAXB

«DateAdapter работает как плагин для JAXB, и мы собираемся прикрепить его к нашему полю даты, используя аннотацию @XmlJavaTypeAdapter. Аннотация @XmlJavaTypeAdapter указывает использование XmlAdapter для пользовательской десортировки:

@XmlRootElement(name = "book")
public class BookDateAdapter {

    // same as before

    @XmlElement(name = "published", required = true)
    @XmlJavaTypeAdapter(DateAdapter.class)
    private Date published;

    // same as before

}

Мы также используем стандартные аннотации JAXB: аннотации @XmlRootElement и @XmlElement.

Наконец, давайте запустим новый код:

[title: Book1; published: Wed Nov 28 02:31:32 EET 1979]

5. Разупорядочение дат в Java 8

В Java 8 появился новый API даты/времени. Здесь мы сосредоточимся на классе LocalDateTime, который является одним из наиболее часто используемых.

5.1. Создание XmlAdapter на основе LocalDateTime

По умолчанию JAXB не может автоматически привязывать значение xsd:dateTime к объекту LocalDateTime независимо от формата даты. Чтобы преобразовать значение даты XML-схемы в объект LocalDateTime или из него, нам нужно определить другой XmlAdapter, аналогичный предыдущему:

public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {

    private DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public String marshal(LocalDateTime dateTime) {
        return dateTime.format(dateFormat);
    }

    @Override
    public LocalDateTime unmarshal(String dateTime) {
        return LocalDateTime.parse(dateTime, dateFormat);
    }

}

В этом случае мы использовали DateTimeFormatter вместо SimpleDateFormat. Первый был представлен в Java 8 и совместим с новым API даты/времени.

Обратите внимание, что операции преобразования могут совместно использовать объект DateTimeFormatter, поскольку DateTimeFormatter является потокобезопасным.

5.2. Интеграция нового адаптера

Теперь давайте заменим старый адаптер новым в нашем классе Book, а также Date на LocalDateTime:

@XmlRootElement(name = "book")
public class BookLocalDateTimeAdapter {

    // same as before

    @XmlElement(name = "published", required = true)
    @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    private LocalDateTime published;

    // same as before

}

Если мы запустим приведенный выше код, мы получим вывод: ~~ ~

[title: Book1; published: 1979-11-28T02:31:32]

Обратите внимание, что LocalDateTime.toString() добавляет разделитель «T» между датой и временем.

6. Заключение

В этом уроке мы рассмотрели неупорядочение дат с помощью JAXB.

Сначала мы рассмотрели сопоставление XML-схемы с типом данных Java и создали пример, используя формат даты XML-схемы по умолчанию.

Затем мы узнали, как использовать настраиваемый формат даты на основе пользовательского XmlAdapter, и увидели, как обеспечить безопасность потоков SimpleDateFormat.

Наконец, мы использовали превосходный поточно-ориентированный API даты/времени Java 8 и неупорядоченные даты с пользовательскими форматами.

Как всегда, исходный код, использованный в руководстве, доступен на GitHub.