Демаршалювання дат за допомогою JAXB

1. Вступ

У цьому підручнику ми побачимо, як зняти маршал об’єктів дат з різними форматами за допомогою JAXB.

Спочатку ми розглянемо формат дати схеми за замовчуванням. Потім ми дослідимо, як використовувати різні формати. Ми також побачимо, як ми можемо впоратися із загальним викликом, який виникає з цими техніками.

2. Схема прив’язки Java

По-перше, нам слід зрозуміти взаємозв'язок між схемою XML та типами даних Java . Зокрема, нас цікавить зіставлення між XML-схемою та об’єктами дати Java.

Відповідно до відображення Schema to Java існує три типи даних Schema, які нам потрібно враховувати: xsd: date , xsd: time і xsd: dateTime . Як бачимо, усі вони відображаються у javax.xml.datatype.XMLGregorianCalendar .

Ми також повинні розуміти формати за замовчуванням для цих типів схеми XML. У XSD: дата і XSD: час типи даних мають « YYYY-MM-DD» і « чч: мм: сс» форматів. Формат xsd: dateTime - “ РРРР-ММ-ДДТгг: мм: сс”, де “ Т” - роздільник, що вказує на початок часового відрізка.

3. Використання формату дати схеми за замовчуванням

Ми збираємося побудувати приклад, що немаршали датують об’єкти. Зупинимось на типі даних xsd: dateTime, оскільки це надмножина інших типів.

Давайте використаємо простий файл XML, який описує книгу:

 Book1 1979-10-21T03:31:12 

Ми хочемо зіставити файл із відповідним об'єктом 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 у похідні від JAXB об’єкти Java:

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

У наведеному вище коді ми визначили JAXBContext, який є точкою входу в API JAXB. Потім ми використали JAXB Unmarshaller у вхідному потоці для того, щоб прочитати наш об’єкт:

Якщо запустити наведений вище код і надрукувати результат, ми отримаємо такий об’єкт Book :

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

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

4. Використання нестандартного формату дати

Наведений приклад працює, оскільки ми використовуємо формат дати схеми за замовчуванням, “РРРР-ММ-ДДТгг: мм: сс”.

Але що, якщо ми хочемо використовувати інший формат, такий як “РРРР-ММ-ДД чч: мм: сс”, позбувшись роздільника “Т” ? Якби ми замінили роздільник на пробіл у нашому файлі XML, демаршалінг за замовчуванням не вдався.

4.1. Створення користувацького XmlAdapter

Для того, щоб використовувати інший формат дати, нам потрібно визначити XmlAdapter .

Давайте також побачимо, як зіставити тип xsd: dateTime з об’єктом java.util.Date за допомогою нашого користувацького XmlAdapter:

public class DateAdapter extends XmlAdapter { 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 не може зробити за замовчуванням.

Для того, щоб створити власний адаптер Xml , ми повинні замінити два методи: XmlAdapter.marshal () та XmlAdapter.unmarshal () .

Під час демаршалювання механізм прив'язки JAXB спочатку демаршує представлення XML до рядка, а потім викликає DateAdapter.unmarshal () для адаптації типу значення до дати . Під час маршування механізм прив'язки JAXB викликає DateAdapter.marshal () для адаптації дати до рядка , який потім маршується до представлення XML.

4.3. Інтеграція за допомогою анотацій JAXB

DateAdapter працює як плагін до JAXB , і ми збираємося , щоб приєднати його до нашої датою полю з допомогою @XmlJavaTypeAdapter анотації. @XmlJavaTypeAdapte г анотації визначають використання 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

By default, JAXB cannot automatically bind an xsd:dateTime value to a LocalDateTime object regardless of the date format. In order to convert an XML Schema date value to or from a LocalDateTime object, we need to define another XmlAdapter similar to the previous one:

public class LocalDateTimeAdapter extends XmlAdapter { 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); } }

In this case, we've used a DateTimeFormatter instead of a SimpleDateFormat. The former was introduced in Java 8 and it's compatible with the new Date/Time API.

Note that the conversion operations can share a DateTimeFormatter object because the DateTimeFormatter is thread-safe.

5.2. Integrating the New Adapter

Now, let's replace the old adapter with the new one in our Book class and also Date with LocalDateTime:

@XmlRootElement(name = "book") public class BookLocalDateTimeAdapter { // same as before @XmlElement(name = "published", required = true) @XmlJavaTypeAdapter(LocalDateTimeAdapter.class) private LocalDateTime published; // same as before }

If we run the above code, we'll get the output:

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

Note that the LocalDateTime.toString() adds the “T” delimiter between date and time.

6. Conclusion

In this tutorial, we explored unmarshalling dates using JAXB.

First, we looked at the XML Schema to Java data type mapping and created an example using the default XML Schema date format.

Next, we learned how to use a custom date format based on a custom XmlAdapter and saw how to handle the thread safety of SimpleDateFormat.

Нарешті, ми скористалися чудовим, безпечним для потоків API Java 8 Date / Time та немаршованими датами за допомогою спеціальних форматів.

Як завжди, вихідний код, використаний у навчальному посібнику, доступний на GitHub.