«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.