Unmarshalling delle date utilizzando JAXB

1. Introduzione

In questo tutorial, vedremo come unmarshalling di oggetti di data con formati diversi usando JAXB.

Innanzitutto, tratteremo il formato della data dello schema predefinito. Quindi, esploreremo come utilizzare diversi formati. Vedremo anche come possiamo gestire una sfida comune che si presenta con queste tecniche.

2. Schema a Java Binding

Innanzitutto, dobbiamo comprendere la relazione tra lo schema XML e i tipi di dati Java . In particolare, siamo interessati alla mappatura tra uno schema XML e gli oggetti data Java.

Secondo la mappatura da Schema a Java , ci sono tre tipi di dati Schema che dobbiamo prendere in considerazione: xsd: date , xsd: time e xsd: dateTime . Come possiamo vedere, tutti sono mappati su javax.xml.datatype.XMLGregorianCalendar .

Dobbiamo anche comprendere i formati predefiniti per questi tipi di XML Schema. I tipi di dati xsd: date e xsd: time hanno i formati " AAAA-MM-GG" e " hh: mm: ss" . Il formato xsd: dateTime è " AAAA-MM-GGThh: mm: ss" dove " T" è un separatore che indica l'inizio della sezione dell'ora.

3. Utilizzo del formato data dello schema predefinito

Costruiremo un esempio che unmarshals data gli oggetti. Concentriamoci sul tipo di dati xsd: dateTime perché è un superset degli altri tipi.

Usiamo un semplice file XML che descrive un libro:

 Book1 1979-10-21T03:31:12 

Vogliamo mappare il file all'oggetto Java Book corrispondente :

@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() + "]"; } }

Infine, dobbiamo creare un'applicazione client che converta i dati XML in oggetti Java derivati ​​da JAXB:

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

Nel codice precedente, abbiamo definito un JAXBContext che è il punto di ingresso nell'API JAXB. Quindi, abbiamo utilizzato un JAXB Unmarshaller su un flusso di input per leggere il nostro oggetto:

Se eseguiamo il codice sopra e stampiamo il risultato, otterremo il seguente oggetto Book :

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

Dobbiamo notare che, anche se la mappatura predefinita per xsd: dateTime è XMLGregorianCalendar , avremmo anche potuto utilizzare i tipi Java più comuni: java.util.Date e java.util.Calendar , secondo la guida utente JAXB.

4. Utilizzo di un formato data personalizzato

L'esempio precedente funziona perché stiamo utilizzando il formato di data dello schema predefinito, "AAAA-MM-GGThh: mm: ss".

Ma cosa succede se vogliamo utilizzare un altro formato come "AAAA-MM-GG hh: mm: ss", eliminando il delimitatore "T" ? Se dovessimo sostituire il delimitatore con uno spazio nel nostro file XML, l'unmarshalling predefinito fallirebbe.

4.1. Creazione di un XmlAdapter personalizzato

Per utilizzare un formato di data diverso, è necessario definire un XmlAdapter .

Vediamo anche come mappare il tipo xsd: dateTime a un oggetto java.util.Date con il nostro XmlAdapter personalizzato :

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); } }

In questo adattatore, abbiamo utilizzato SimpleDateFormat per formattare la nostra data. Dobbiamo stare attenti a come il SimpleDateFormat non è thread-safe. Per evitare che più thread abbiano problemi con un oggetto SimpleDateFormat condiviso , ne creiamo uno nuovo ogni volta che ne abbiamo bisogno.

4.2. Il XMLAdapter Internals 's

Come possiamo vedere, XmlAdapter ha due parametri di tipo , in questo caso String e Date . Il primo è il tipo utilizzato all'interno dell'XML ed è chiamato tipo di valore. In questo caso, JAXB sa come convertire un valore XML in una stringa . Il secondo è chiamato tipo associato e si riferisce al valore nel nostro oggetto Java.

L'obiettivo di un adattatore è convertire tra il tipo di valore e un tipo associato, in un modo che JAXB non può fare per impostazione predefinita.

Per creare un adattatore Xml personalizzato , dobbiamo sovrascrivere due metodi: XmlAdapter.marshal () e XmlAdapter.unmarshal () .

Durante l'unmarshalling, il framework di binding JAXB rimuove prima la rappresentazione XML in una stringa e quindi richiama DateAdapter.unmarshal () per adattare il tipo di valore a una data . Durante il marshalling, il framework di binding JAXB richiama DateAdapter.marshal () per adattare una Date a String , che viene quindi sottoposto a marshalling in una rappresentazione XML.

4.3. Integrazione tramite le annotazioni JAXB

Il DateAdapter funziona come un plugin per JAXB e lo collegheremo al nostro campo data utilizzando l' annotazione @XmlJavaTypeAdapter . L' annotazione @XmlJavaTypeAdapte r specifica l'uso di un XmlAdapter per l'unmarshalling personalizzato :

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

Stiamo anche utilizzando le annotazioni JAXB standard: annotazioni @XmlRootElement e @XmlElement .

Infine, eseguiamo il nuovo codice:

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

5. Unmarshalling delle date in Java 8

Java 8 ha introdotto una nuova API di data / ora . Qui ci concentreremo sulla classe LocalDateTime , che è una delle più comunemente utilizzate.

5.1. Compilazione di un XmlAdapter basato su 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.

Infine, abbiamo sfruttato l'API Data / Ora Java 8 superiore, thread-safe e le date non condivise con formati personalizzati.

Come sempre, il codice sorgente utilizzato nel tutorial è disponibile su GitHub.