1. Introduzione a JAXB
JAXB (Java Architecture for XML Binding) è una tecnologia standard di Java per convertire (binding) oggetti Java in XML e viceversa. Con JAXB è facile serializzare gli oggetti in file XML e poi ricrearli da tali file.
JAXB faceva parte della libreria standard di Java fino alla versione 11 inclusa. A partire da Java 11, JAXB è stato spostato in un modulo separato che va aggiunto tramite Maven/Gradle o scaricato manualmente. Per le versioni moderne di Java aggiungete le dipendenze:
<!-- Esempio per Maven -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.3</version>
</dependency>
Perché mai serve XML?
- XML è un formato universale e leggibile dall’uomo, ampiamente usato per lo scambio di dati tra sistemi, per la configurazione e l’archiviazione delle informazioni.
- A differenza della serializzazione binaria, l’XML è facile da leggere, da validare tramite schema e da aprire in un browser.
2. Classi e annotazioni principali di JAXB
JAXB funziona sulla base di annotazioni con cui si marcano le classi e i loro campi per controllare il processo di serializzazione/deserializzazione.
Annotazioni principali
| Annotazione | A cosa serve |
|---|---|
|
Indica l’elemento radice XML (la classe stessa) |
|
Contrassegna un campo/proprietà come elemento XML |
|
Contrassegna un campo/proprietà come attributo XML |
|
Controlla l’ordine degli elementi, il nome del tipo, ecc. |
|
Esclude il campo dalla serializzazione |
Classi principali
- JAXBContext — punto d’ingresso, crea il contesto per la serializzazione/deserializzazione di classi specifiche.
- Marshaller — trasforma un oggetto in XML (marshalling, marshal()).
- Unmarshaller — trasforma XML in un oggetto (unmarshalling, unmarshal()).
3. Esempio: serializzazione di un oggetto in XML
Creiamo una classe da serializzare. Sia un personaggio per il nostro gioco:
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlAttribute;
@XmlRootElement(name = "player")
public class Player {
private String name;
private int level;
private int health;
public Player() {} // Costruttore vuoto obbligatorio!
public Player(String name, int level, int health) {
this.name = name;
this.level = level;
this.health = health;
}
@XmlElement
public String getName() {
return name;
}
public void setName(String name) { this.name = name; }
@XmlElement
public int getLevel() {
return level;
}
public void setLevel(int level) { this.level = level; }
@XmlAttribute
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
}
- @XmlRootElement(name = "player") — la classe diventa l’elemento radice <player>.
- @XmlElement — il campo sarà un elemento XML separato (<name>, <level>).
- @XmlAttribute — il campo sarà un attributo dell’elemento radice (health="100").
- Non dimenticate il costruttore vuoto! JAXB lo richiede per la deserializzazione.
Serializzare un oggetto in XML
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;
public class Main {
public static void main(String[] args) throws Exception {
Player player = new Player("Aragorn", 5, 100);
JAXBContext context = JAXBContext.newInstance(Player.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); // Output leggibile
marshaller.marshal(player, System.out); // Scriviamo l'XML sulla console
// marshaller.marshal(player, new File("player.xml")); // Oppure su file
}
}
Risultato:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<player health="100">
<name>Aragorn</name>
<level>5</level>
</player>
Deserializzazione di un oggetto da XML
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;
import java.io.File;
public class Main {
public static void main(String[] args) throws Exception {
JAXBContext context = JAXBContext.newInstance(Player.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Player player = (Player) unmarshaller.unmarshal(new File("player.xml"));
System.out.println(player.getName() + ", livello: " + player.getLevel() + ", salute: " + player.getHealth());
}
}
4. Caratteristiche e limitazioni di JAXB
Requisiti per le classi
- Costruttore pubblico senza parametri — obbligatorio.
- Per un funzionamento corretto usate getter e setter.
- Tutti i campi serializzabili devono essere accessibili (tramite API pubblica).
- Anche gli oggetti annidati e le collezioni devono essere serializzabili (annotateli e aggiungete un costruttore vuoto).
Lavorare con collezioni e oggetti annidati
Supponiamo che il giocatore abbia un inventario (lista di oggetti). Come serializzare una collezione?
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;
@XmlRootElement(name = "player")
public class Player {
// ... altri campi
private List<String> inventory;
public Player() {}
// ... altri getter/setter
@XmlElementWrapper(name = "inventory")
@XmlElement(name = "item")
public List<String> getInventory() {
return inventory;
}
public void setInventory(List<String> inventory) {
this.inventory = inventory;
}
}
Risultato della serializzazione:
<player health="100">
<name>Aragorn</name>
<level>5</level>
<inventory>
<item>Sword</item>
<item>Shield</item>
</inventory>
</player>
- @XmlElementWrapper — crea un «involucro» attorno alla collezione (l’elemento <inventory>).
- @XmlElement(name = "item") — ogni elemento della lista viene serializzato come <item>.
Se avete oggetti annidati (ad esempio, Position), vanno anch’essi annotati e dotati di un costruttore vuoto.
5. Pratica: serializzare e deserializzare un oggetto in XML
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlAttribute;
import java.util.List;
@XmlRootElement(name = "player")
public class Player {
private String name;
private int level;
private int health;
private List<String> inventory;
private Position position;
public Player() {}
public Player(String name, int level, int health, List<String> inventory, Position position) {
this.name = name;
this.level = level;
this.health = health;
this.inventory = inventory;
this.position = position;
}
@XmlElement
public String getName() { return name; }
@XmlElement
public int getLevel() { return level; }
@XmlAttribute
public int getHealth() { return health; }
@XmlElementWrapper(name = "inventory")
@XmlElement(name = "item")
public List<String> getInventory() { return inventory; }
@XmlElement
public Position getPosition() { return position; }
// setter omessi per brevità
}
@XmlRootElement(name = "position")
class Position {
private int x;
private int y;
public Position() {}
public Position(int x, int y) { this.x = x; this.y = y; }
@XmlAttribute
public int getX() { return x; }
@XmlAttribute
public int getY() { return y; }
// setter omessi
}
Serializzazione:
Player player = new Player(
"Aragorn",
5,
100,
List.of("Sword", "Shield", "Potion"),
new Position(10, 20)
);
JAXBContext context = JAXBContext.newInstance(Player.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(player, System.out);
Risultato XML:
<player health="100">
<name>Aragorn</name>
<level>5</level>
<inventory>
<item>Sword</item>
<item>Shield</item>
<item>Potion</item>
</inventory>
<position x="10" y="20"/>
</player>
La deserializzazione funziona in modo analogo: JAXB gestirà automaticamente oggetti annidati e collezioni se le classi sono descritte correttamente.
6. Tabella: annotazioni principali di JAXB e loro effetto
| Annotazione | Dove usarla | Cosa produce in XML |
|---|---|---|
|
Classe | Elemento radice |
|
Getter/campo | Elemento interno XML |
|
Getter/campo | Attributo dell’elemento |
|
Getter della collezione | «Involucro» della collezione (per esempio, <list>) |
|
Campo/getter | Esclude il campo dalla serializzazione |
|
Classe | Controlla l’ordine degli elementi, il nome del tipo |
7. Caratteristiche e limitazioni di JAXB
Ordine degli elementi
Per impostazione predefinita JAXB può emettere gli elementi in ordine alfabetico. Per specificare esplicitamente l’ordine, usate @XmlType e la proprietà propOrder:
@XmlType(propOrder = {"name", "level", "inventory", "position"})
Esclusione di campi
Per non serializzare un campo/getter, usate @XmlTransient:
@XmlTransient
public String getSecretCode() { ... }
Problemi con le collezioni
- Non usate collezioni «raw» senza generics: scrivete List<Type>, non List.
- Se la collezione contiene oggetti, anche le loro classi devono essere annotate e avere un costruttore vuoto.
Errori
- Manca il costruttore vuoto — si otterrà una JAXBException durante l’unmarshalling.
- Classe annidata non annotata — JAXB non riuscirà a serializzarla/deserializzarla.
- Tipi non standard (ad esempio, LocalDate) richiedono un adapter (@XmlJavaTypeAdapter).
8. Errori tipici nell’uso di JAXB
Errore n. 1: assenza del costruttore vuoto. JAXB richiede che la classe serializzabile abbia un costruttore pubblico senza parametri. In caso contrario — durante l’unmarshalling si verificherà l’eccezione JAXBException.
Errore n. 2: oggetti annidati non annotati. Se avete un campo‑oggetto, ma la sua classe non è annotata con @XmlRootElement o almeno con @XmlType, JAXB non riuscirà a serializzarlo/deserializzarlo correttamente.
Errore n. 3: problemi con le collezioni. JAXB non gestisce collezioni «raw» senza indicazione del tipo degli elementi. Usate i generics e annotate correttamente le collezioni (@XmlElementWrapper + @XmlElement).
Errore n. 4: gestione implicita dell’ordine degli elementi. Se l’ordine degli elementi in XML è importante per l’integrazione, usate @XmlType con propOrder; altrimenti JAXB può emettere gli elementi in un ordine diverso (ad es. alfabetico).
Errore n. 5: uso di tipi non standard senza adapter. JAXB non sa serializzare alcuni tipi (ad esempio, LocalDate) senza un adapter. Applicate @XmlJavaTypeAdapter oppure serializzate il valore come stringa.
GO TO FULL VERSION