Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

JAXB, l'XML in Java: esempi e tutorial

Un set di API per semplificare l’accesso e la realizzazione di documenti XML attraverso applicazioni scritte direttamente in Java.
Un set di API per semplificare l’accesso e la realizzazione di documenti XML attraverso applicazioni scritte direttamente in Java.
Link copiato negli appunti

JAXB (Java Architecture for XML Binding) è un framework a supporto dell’utilizzo dell’XML in Java. L’Extensible Markup Language (XML) e la tecnologia Java sono partner naturali nel supporto allo scambio di dati in Internet: la stessa accoppiata XML+Java è spesso utilizzata per realizzare web services e relative applicazioni d’accesso. PArtendo da queste premesse JAXB mette a disposizione un set di API per semplificare l’accesso e la realizzazione di documenti XML attraverso applicazioni scritte direttamente in Java, e dalla versione 1.6, JAXB è incluso in Java SE.

Esistonoo ormai diverse applicazioni che permettono di gestire l’accoppiata XML+Java: le due più comuni sono il SAX (Simple API for XML) e il DOM (Document Object Model), entrambi appartenenti al framework JAXP (Java API for XML Processing) ed utilizzate per effettuare l’analisi di un documento XML.

Nell’approccio SAX il documento XML viene esplorato dal principio alla fine senza salvare nulla in memoria, per cui non è possibile eseguire operazioni che richiedono la memorizzazione del documento stesso o di parti di esso, ma i vari contenuti vengono utilizzati via via che vengono letti (ed analizzati) all'interno del documento.

Nell’approccio DOM viene invece creato in memoria un albero rappresentante l’oggetto, per cui un’applicazione può navigare il documento attraverso l’albero e, se necessario, manipolare i dati.

Ciò che consente di fare JAXB è accedere e processare l’XML senza dover conoscere il contenuto del documento stesso e senza dover lavorare "pensando" in XML. Le operazioni principali per poter elaborare un documento XML sono le operazioni di Bind e di Unmarshall. Come approfondiremo successivamente, con l’operazione di Bind si semplifica l’operazione di accesso a un documento XML partendo dal corrispondente schema XML. Con l’Unmarshall invece si ottengono degli oggetti Java che rappresentano il contenuto e l’organizzazione del documento XML. Come vedremo, è possibile anche l’operazione duale, il Marshalling.

L’articolo è costituito dalle seguenti sezioni:

  • Precondizioni
  • Binding
  • Unmarshalling, Marshalling e utility
  • Conclusioni

Precondizioni: Java Development Kit

Assicurarsi di avere correttamente installato sul computer il Java Development Kit (JDK 6, Update 24 o successiva). Si assume che sia stata correttamente impostata la variabile d’ambiente JAVA_HOME e aggiornato il Path di sistema. Si fa inoltre riferimento a Eclipse (a partire dalla versione Helios Sr2 3.6.2).

E’ inoltre utile avere conoscenze basiche sull’argomento, quindi occorre aver affrontato gli argomenti XML e XML schema, consigliamo l’utile guida XML di base, su queste pagine.

Binding JAXB

JAXB semplifica l’accesso a documenti XML da programmi Java, presentando il documento XML in un formato di facile elaborazione. Il primo passo in questo processo è quello di effettuare il Binding del corrispondente XML schema. Ciò significa generare un insieme di classi Java che rappresentano tale schema. Tutte le implementazioni di JAXB forniscono un tool per richiamare il compilatore (binding compiler) che effettua il bind dello schema. Quello che può cambiare è il modo in cui viene invocato di volta in volta il compilatore. Da sottolineare che, poiché le classi generate sono legate all’implementazione specifica di JAXB, classi generate da un compilatore potrebbero non funzionare correttamente con un'altra implementazione di JAXB.

In questo articolo si fa riferimento all’implementazione di riferimento (JAXB Reference Implementation), che fornisce un compilatore invocabile tramite script, l’xjc. Supponendo siano soddisfatte le precondizioni indicate, provvediamo a creare una cartella per il nostro primo esempio.

Utilizzeremo come esempio uno schema molto semplice, che descrive le caratteristiche di una pianta. Per comodità creeremo una cartella contenente i nostri esempi, che chiameremo JAXBesempi, e due sottocartelle All e Schemi.

Schemi XML

Nella sottocartella All creiamo il file XSD Pianta.xsd, per poi popolarlo con:

Pianta.xsd

<?xml version="1.0" encoding="ISO-8859-1" ?>
<xs:schema
xmlns:sc2="http://www.flora.bindings.com"
xmlns:sc1="http://www.elementiBase.bindings.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.flora.bindings.com"
>
<xs:import namespace="http://www.elementiBase.bindings.com" schemaLocation="../Schemi/Radice.xsd" />
<xs:import namespace="http://www.elementiBase.bindings.com" schemaLocation="../Schemi/Fusto.xsd" />
<xs:import namespace="http://www.elementiBase.bindings.com" schemaLocation="../Schemi/Foglia.xsd" />
<xs:element name="Pianta">
<xs:complexType>
<xs:sequence>
<xs:element name="Nome" element="xs:string" />
<xs:element ref="sc1:TipologiaRadice" />
<xs:element ref="sc1:CaratteristicheFusto" />
<xs:element ref="sc1:CaratteristicheFoglie" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Quello appena proposto è un semplice schema d’esempio che descrive sommariamente le caratteristiche principali di una pianta, richiamando a sua volta altri tre schemi per fornire ulteriori dettagli.

Come mostrato dagli import questi schemi si troveranno nella sottocartella Schemi, che andiamo ora a popolare:

Radice.xsd

<?xml version="1.0" encoding="ISO-8859-1" ?>
<xs:schema
xmlns:sc1="http://www.elementiBase.bindings.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.elementiBase.bindings.com"
>
<xs:import namespace="http://www.w3.org/2001/XMLSchema" />
<xs:element name="TipologiaRadice">
<xs:complexType>
<xs:sequence>
<xs:element name="Ramificazione" type="xs:string" />
<xs:element name="Resistenza" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Fusto.xsd

<?xml version="1.0" encoding="ISO-8859-1" ?>
<xs:schema
xmlns:sc1="http://www.elementiBase.bindings.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.elementiBase.bindings.com"
>
<xs:import namespace="http://www.w3.org/2001/XMLSchema" />
<xs:element name="CaratteristicheFusto">
<xs:complexType>
<xs:sequence>
<xs:element name="Tipo" type="xs:string" />
<xs:element name="Spessore" type="xs:int" />
<xs:element name="Altezza" type="xs:int" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Foglia.xsd

<?xml version="1.0" encoding="ISO-8859-1" ?>
<xs:schema
xmlns:sc1="http://www.elementiBase.bindings.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.elementiBase.bindings.com"
>
<xs:import namespace="http://www.w3.org/2001/XMLSchema" />
<xs:element name="CaratteristicheFoglie">
<xs:complexType>
<xs:sequence>
<xs:element name="Tipo" type="xs:string" />
<xs:element name="Forma" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

La situazione sarà quindi la seguente:

- JAXBesempi
       --All
             ---Pianta.xsd
       --Schemi
             ---Foglia.xsd
             ---Fusto.xsd
             ---Radice.xsd

Generazione delle classi Java

Partendo da questi schemi con il Binding possiamo generare le relative classi Java. Per farlo apriamo una console e muoviamoci nella cartella All, lanciando il comando:

xjc -nv Pianta.xsd

Il seguente screenshot mostra il risultato atteso (nell'esempio su windows):

JAXB: generazione delle classi da riga di comando

Nel rispetto del namespace impostato, sono state generate le classi corrispondenti agli elementi definiti negli schemi importati e in quello che li richiama. Esplorando le classi generate è possibile notare che il mapping tra XML Schema e Java viene implementato mediante l’uso delle annotazioni. Per ogni package definito dal namespace vengono inoltre generate due classi, ObjectFactory e package-info.

package-info

Il package-info è il descrittore del package, l’holder delle annotazioni JAXB, con la responsabilità di importare le librerie contenenti le annotazioni JAXB.

L’ObjectFactory ha invece lo scopo di permettere di creare oggetti a tempo d’esecuzione in risposta ad azioni dell’utente o di un flusso di informazioni, oggetti che al tempo della compilazione risultano di tipo sconosciuto. Più in dettaglio, fornisce quindi metodi Factory per le interfacce generate, permettendo la generazione di nuove istanze delle operazioni definite nello schema. Approfondiremo questo aspetto in seguito.

Attraverso le opzioni del tool è possibile specificare diversi aspetti come la cartella di generazione dei file, eventuali file di bindings esterni, il livello di verbosità e così via. Nell’esempio mostrato è stata utilizzata l’opzione -nv. Questa opzione è una direttiva al compilatore per indicare di non effettuare una validazione con regole stringenti degli schemi di input. Nel nostro caso, una validazione stringente avrebbe sollevato un simile messaggio di errore:

Cannot resolve the name 'sc1:CaratteristicheFusto' to a(n) 'element declaration’ component

É anche possibile lanciare il tool su un singolo schema che non ne richiama altri, ad esempio lo lanciamo sullo schema Foglia.xsd:

xjc -nv Foglia.xsd
JAXB: generazione delle classi per lo schema foglia.xsd

Questa volta, oltre all’ObjectFactory e al package-info, è stata generata solo la classe CaratteristicheFoglie.

Una volta ottenuti i package con le relative classi sarà possibile importarli in un progetto esistente (ad esempio nell’IDE Eclipse) o utilizzarli come base per un nuovo progetto. Da notare che le classi generate forniscono i metodi di get e set (in pieno stile java beans) per specificare ed accedere ai dati per ogni tipo di elemento e attributo definito negli schemi.

Unmarshalling, Marshalling e utility

Unmarshalling

Effettuare l’unmarshalling di un documento XML significa creare un albero di oggetti che rappresenta il contenuto e l’organizzazione del documento. Gli oggetti creati sono istanze delle classi prodotte dal binding compiler. Per effettuare un’operazione di unmarshalling occorre istanziare un oggetto JAXBContext, un oggetto Unmarshaller e invocare il metodo per effettuare l’unmarshalling.

Il JAXBContext fornisce l’entry point per le JAXB API. Occorre specificare il contesto, che rappresenta una lista di uno o più nomi di package che contengono le interfacce generate da un binding compiler. Se gli schemi utilizzati sono ben strutturati, è sufficiente passare il nome del package contentente la classe contenitore del documento, ossia l’entry point, come vedremo successivamente.

L’oggetto Unmarshaller controlla il processo di unmarshalling, fornendo un metodo che invocato permette di effettuare l’unmarshalling del documento XML passato sotto forma di File. Ciò che viene restituito è un oggetto, oggetto che può essere convertito nella classe contenitore con una operazione di casting. Questo aspetto è importante perché come vedremo ci consentirà di creare una classe generica utilizzabile per l’unmarshalling di tutti i documenti XML. Vediamo come.

Si suppone che nel progetto si siano importate le classi generate dal binding compiler, rispettando i nomi dei package creati (conseguentemente sotto la cartella src troveremo i package com.bindings.elementibase e com.bindings.flora, oltre ad almeno un altro package, ad esempio example, dove inserire le classi di prova).

public class JaxbUnmarshal {
String filePath;
String context;
JAXBContext jaxbContext;
InputStream InpStr;
public JaxbUnmarshal(String filePath, String context){
this.filePath=filePath;
this.context=context;
try {
jaxbContext = JAXBContext.newInstance (context);
} catch (JAXBException e) {
e.printStackTrace();
}
}
public Object getUnmarshalledObject(){
Unmarshaller unmarshaller=null;
Object objectJAXB=null;
try {
unmarshaller = jaxbContext.createUnmarshaller ();
objectJAXB = unmarshaller.unmarshal( new
FileInputStream(filePath));
} catch (JAXBException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return objectJAXB;
}
}

La classe mostrata richiede il percorso per accedere al file XML di cui effettuare l’unmarshalling e il contesto. Tramite il metodo getUnmarshalledObject() verrà ritornato un oggetto generico. Ciò consente di adoperare tale classe indipendentemente dal tipo di documento XML di cui si voglia effettuare l’unmarshalling, sempreché si disponga delle classi generate dal binding compiler.
Nel nostro caso possiamo provare ad effettuare l’unmarshalling del documento pianta.xml, collocato nella cartella xmlExample:

<?xml version="1.0" encoding="UTF-8"?>
<ns3:Pianta xmlns:ns2="http://www.elementiBase.bindings.com" xmlns:ns3="http://www.flora.bindings.com">
<Nome>Quercia</Nome>
<ns2:TipologiaRadice>
<Ramificazione>decisa</Ramificazione>
<Resistenza>alta</Resistenza>
</ns2:TipologiaRadice>
<ns2:CaratteristicheFusto>
<Tipo>Dritto</Tipo>
<Spessore>1</Spessore>
<Altezza>15</Altezza>
</ns2:CaratteristicheFusto>
<ns2:CaratteristicheFoglie>
<Tipo>Lobate</Tipo>
<Forma>Ellittica</Forma>
</ns2:CaratteristicheFoglie>
</ns3:Pianta>

Notare in primo luogo l’attenzione posta al namespace nella realizzazione dell’XML. Se il namespace non è correttamente specificato, l’operazione di unmarshalling non produrrà il risultato voluto.

Ora vediamo come utilizzare la classe mostrata:

String filePath = "xmlExample/pianta.xml";
String context = "com.bindings.flora";
JaxbUnmarshal unmarshall = new JaxbUnmarshal(filePath, context);
Pianta piantaObj = (Pianta) unmarshall.getUnmarshalledObject();

Il package passato è quello che contiene la classe contenitore Pianta, ossia com.bindings.flora. Se avessimo voluto effettuare l’unmarshalling del file foglie.xml invece avremmo passato come context la stringa com.bindings.elementibase.

Nel richiamare il metodo per effettuare l’unmarshalling viene effettuato il casting, in questo modo la classe JaxbUnmarshal rimane generica e può essere successivamente adoperata per altri file, come il file foglie.xml.

É comunque possibile accedere ai dati senza effettuare l’unmarshalling di un documento XML, in quanto le classi ObjectFactory generate dal compilatore mettono a disposizione metodi per creare oggetti del tipo voluto, esattamente come si fa con le comuni classi Java. Una volta istanziato l’oggetto della classe desiderata, andrà popolato utilizzando i metodi di set. Segue un esempio.

Pianta nuovapianta = new Pianta();
nuovapianta.setNome("Alloro");
CaratteristicheFusto fustoAlloro = new CaratteristicheFusto();
fustoAlloro.setSpessore(1);
fustoAlloro.setAltezza(8);
nuovapianta.setCaratteristicheFusto(fustoAlloro);

Da notare che se si volesse impostare direttamente i valori delle caratteristiche del fusto senza passare la corrispondente classe tramite il metodo setCaratteristicheFusto(), verrebbe sollevata un’eccezione in quanto la classe cui si fa riferimento non è stata ancora inizializzata.

Un metodo significativo della classe Unmarshaller è quello che consente di effettuare la validazione del documento, ossia il metodo setValidating(). Questo metodo valida la sorgente dati nei confronti dello schema XML. In riguardo, le specifiche di JAXB obbligano le implementazioni a riportare la presenza di errori nell’elaborazione del documento, mentre ciò che segue è lasciato alle singole implementazioni. E’ pertanto possibile riscontrare comportamenti diversi che possono prevedere un’interruzione forzata fin dalla prima infrazione dello schema, o che l’unmarshalling venga portato a compimento, magari limitandosi a fornire l’elemento padre senza ulteriori elementi. In altre parole, è possibile che l’albero venga creato, ma che ci sia un risultato invalidato o non corrispondente alle intenzioni. E’ comunque possibile evitare la validazione se si vuole evitarne l’overhead, ad esempio nel caso in cui si è sicuri della fonte.

Marshalling

Il marshalling è l’operazione opposta dell’unmarshalling. Partendo da un albero di oggetti JAXB crea il corrispondente documento XML. Più in generale, ciò che consente di fare è serializzare oggetti Java in XML. Detto altrimenti, l’oggetto viene convertito su un supporto di memorizzazione lineare, che può essere un file o un’area di memoria, e/o per trasmetterlo su una connessione di rete. Un esempio di utilizzo del marshalling avviene nei web services. Gli oggetti passati come argomenti delle operazioni remote vengono serializzati per poter essere trasmessi sulla connessione di rete.

Di seguito verrà riportato il caso di serializzazione in una codifica testuale XML. Analogamente all’unmarshalling, occorre specificare il contesto, istanziare un oggetto della classe Marshaller e invocare il metodo marshal(). L’oggetto istanziato possiede alcuni metodi per impostare delle proprietà, ad esempio per ottenere un file XML completo di rientri ed interruzioni di riga è possibile utilizzare l’opzione JAXB_FORMATTED_OUTPUT.

Viene mostrata di seguito una classe che effettua il marshalling.

public class JaxbMarshal {
String filePath;
String context;
JAXBContext jaxbContext;
public JaxbMarshal(String filePath, String context){
this.filePath=filePath;
this.context=context;
try {
jaxbContext = JAXBContext.newInstance(context);
} catch (JAXBException e) {
e.printStackTrace();
}
}
public void getMarshalledFile(Object object){
Marshaller marshaller=null;
try {
marshaller = jaxbContext.createMarshaller ();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
new Boolean(true));
marshaller.marshal(object, new FileOutputStream(filePath));
} catch (JAXBException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}

Vediamo di seguito come utilizzarla.

JaxbMarshal marshall = new JaxbMarshal("xmlExample/outputFile.xml", context);
marshall.getMarshalledFile(nuovapianta);

Si è deciso di effettuare il marshalling dell’oggetto nuovapianta, oggetto già istanziato nei precedenti esempi. Osservare che come path di destinazione del file generato dal marshaller si è scelta la cartella dove si trovavano i file XML che abbiamo utilizzato in precedenza. Nella cartella in questione verrà generato il file outputFile.xml. In alcuni casi, ad esempio se si usa Eclipse, può essere necessario effettuare un refresh (tasto F5) del progetto prima di vedere il file nella cartella di destinazione.

Utility di un oggetto JAXB

Viste le funzionalità base, si presentano di seguito alcune utility che possono risultare estremamente comode. La prima riguarda la stampa a video di un oggetto JAXB. Si presenta di seguito una classe che offre un metodo statico che svolge questo lavoro.

public class StampGenericXML {
static public void staticStampGenericXML(Object objectJAXB, String
context){
try {
JAXBContext jaxbLocalContext = JAXBContext.newInstance
(context);
Marshaller marshaller =
jaxbLocalContext.createMarshaller();
marshaller.setProperty(marshaller.JAXB_FORMATTED_OUTPUT,
true);
marshaller.marshal(objectJAXB, System.out);
} catch (JAXBException e1) {
e1.printStackTrace();
}
}
}

Si fanno notare due aspetti. Il primo è che come in precedenza si richiede un oggetto generico e il relativo contesto. Il secondo è che di fatto l’operazione di stampa a video non è altro che un marshalling effettuato sulla console invece che su file.

Le due classi successive presentano due funzionalità duali. La prima consente di convertire un oggetto JAXB in una comune stringa di testo. La seconda consente invece di invertire il processo tornando all’oggetto originario. Messe in cascata consentono di effettuare l’unmarshalling del marshalling ed, essendo operazioni inverse, il risultato atteso è l’oggetto di partenza.

public class StringMarshalling {
static public String getXMLString(String context, Object objectJAXB){
StringWriter xml = new StringWriter();
String xmlString = new String();
try {
JAXBContext jaxbLocalContext = JAXBContext.newInstance
(context);
jaxbLocalContext.createMarshaller().marshal(objectJAXB,
xml);
xmlString = xml.toString();
} catch (JAXBException e1) {
e1.printStackTrace();
}
return xmlString;
}
}

public class StringUnmarshalling {
String text;
String context;
JAXBContext jaxbContext;
public static Object getObject(String context, String stringToConvert,
boolean displayStamp){
Unmarshaller unmarshaller=null;
Object objectJAXB=null;
try {
JAXBContext jaxbContext = JAXBContext.newInstance
(context);
unmarshaller = jaxbContext.createUnmarshaller();
StringBuffer xmlString = new StringBuffer(stringToConvert);
objectJAXB = unmarshaller.unmarshal(new
StringReader(xmlString.toString()));
} catch (JAXBException e) {
e.printStackTrace();
}
return objectJAXB;
}
}

Come riprova, è possibile effettuare una stampa a video di un oggetto JAXB, ad esempio dell’oggetto piantaObj ottenuto tramite unmarshalling del file pianta.xml. Successivamente è possibile serializzarlo convertendolo in una stringa, per poi deserializzare la stringa riottenendo l’oggetto di partenza, stampando a video l’oggetto finale per confrontarlo con quello iniziale e verificare che si è ottenuto l’oggetto di partenza. Il tutto è riportato di seguito:

StampGenericXML.staticStampGenericXML(piantaObj, context);
String stringPianta = StringMarshalling.getXMLString(context, piantaObj);
Pianta piantaObj2 = (Pianta) StringUnmarshalling.getObject(context, stringPianta);
StampGenericXML.staticStampGenericXML(piantaObj2, context);

Conclusioni su Java XML file

Dopo aver descritto le funzionalità principali del framework JAXB possiamo riassumerne i vantaggi. La caratteristica essenziale è quella di semplificare l’accesso ai documenti XML nei programmi Java. Tramite l’unmarshalling è possibile accedere ed elaborare documenti XML, senza bisogno di utilizzare parser SAX e metodi di callback. L’accesso non è sequenziale e non forza a navigare un albero come avviene in DOM. Altro vantaggio rispetto a DOM è quello di assicurare un uso della memoria più efficiente.

Come abbiamo visto, JAXB è flessibile in quanto è possibile effettuare l’unmarshalling e il marshalling in diverse forme e da diverse sorgenti. E’ infatti possibile effettuare l’unmarshalling da file XML, da stringhe, da URL o anche da un nodo DOM. E’ inoltre possibile utilizzare le classi generate con il binding compiler per creare oggetti senza dover accedere a un file XML già realizzato. Infine, è possibile validare le sorgenti durante l’unmarshalling oppure utilizzare una classe apposita, Validator, per validare un albero già a disposizione.

Ti consigliamo anche