CodeGym /Kursy /JAVA 25 SELF /Praca z XML przez JAXB: podstawy, adnotacje

Praca z XML przez JAXB: podstawy, adnotacje

JAVA 25 SELF
Poziom 47 , Lekcja 3
Dostępny

1. Wprowadzenie do JAXB

JAXB (Java Architecture for XML Binding) — to standardowa technologia Javy do przekształcania (binding) obiektów Java do XML i z powrotem. Z pomocą JAXB można łatwo serializować obiekty do plików XML, a następnie odtwarzać je z tych plików.

JAXB wchodził do standardowej biblioteki Javy do wersji 11 włącznie. Począwszy od Java 11, JAXB został wydzielony do osobnego modułu, który trzeba dołączyć przez Maven/Gradle lub pobrać ręcznie. Dla nowoczesnych wersji Javy dodaj zależności:

<!-- Przykład dla 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>

Po co w ogóle XML?

  • XML to uniwersalny, czytelny dla człowieka format, który jest szeroko używany do wymiany danych między systemami, konfigurowania i przechowywania informacji.
  • W przeciwieństwie do serializacji binarnej, XML można łatwo przeczytać „gołym okiem”, zweryfikować zgodność ze schematem i otworzyć w przeglądarce.

2. Podstawowe klasy i adnotacje JAXB

JAXB działa na podstawie adnotacji, którymi oznacza się klasy i ich pola, aby sterować procesem serializacji/deserializacji.

Najważniejsze adnotacje

Adnotacja Do czego służy
@XmlRootElement
Oznacza korzeniowy element XML (samą klasę)
@XmlElement
Oznacza pole/właściwość jako element XML
@XmlAttribute
Oznacza pole/właściwość jako atrybut XML
@XmlType
Steruje kolejnością elementów, nazwą typu i innymi
@XmlTransient
Wyklucza pole z serializacji

Najważniejsze klasy

  • JAXBContext — punkt wejścia, tworzy kontekst dla serializacji/deserializacji konkretnych klas.
  • Marshaller — zamienia obiekt na XML (marshalling, marshal()).
  • Unmarshaller — zamienia XML na obiekt (unmarshalling, unmarshal()).

3. Przykład: serializacja obiektu do XML

Utwórzmy klasę, którą będziemy serializować. Niech to będzie postać do naszej gry:

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() {} // Obowiązkowy pusty konstruktor!

    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") — klasa staje się elementem korzeniowym <player>.
  • @XmlElement — pole będzie osobnym elementem XML (<name>, <level>).
  • @XmlAttribute — pole będzie atrybutem elementu korzeniowego (health="100").
  • Nie zapomnij o pustym konstruktorze! JAXB wymaga go do deserializacji.

Serializacja obiektu do 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); // Ładny format wyjścia

        marshaller.marshal(player, System.out); // Wypisujemy XML na konsolę
        // marshaller.marshal(player, new File("player.xml")); // Albo do pliku
    }
}

Wynik:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<player health="100">
    <name>Aragorn</name>
    <level>5</level>
</player>

Deserializacja obiektu z 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() + ", poziom: " + player.getLevel() + ", zdrowie: " + player.getHealth());
    }
}

4. Cechy i ograniczenia JAXB

Wymagania dla klas

  • Publiczny konstruktor bez parametrów — obowiązkowy.
  • Dla poprawnego działania używaj getterów i setterów.
  • Wszystkie pola podlegające serializacji muszą być dostępne (przez publiczne API).
  • Obiekty zagnieżdżone i kolekcje także muszą być serializowalne (oznacz je adnotacjami i dodaj pusty konstruktor).

Praca z kolekcjami i obiektami zagnieżdżonymi

Załóżmy, że gracz ma ekwipunek (listę przedmiotów). Jak zserializować kolekcję?

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;

@XmlRootElement(name = "player")
public class Player {
    // ... pozostałe pola

    private List<String> inventory;

    public Player() {}

    // ... pozostałe gettery/settery

    @XmlElementWrapper(name = "inventory")
    @XmlElement(name = "item")
    public List<String> getInventory() {
        return inventory;
    }

    public void setInventory(List<String> inventory) {
        this.inventory = inventory;
    }
}

Wynik serializacji:

<player health="100">
    <name>Aragorn</name>
    <level>5</level>
    <inventory>
        <item>Sword</item>
        <item>Shield</item>
    </inventory>
</player>
  • @XmlElementWrapper — tworzy „opakowanie” wokół kolekcji (element <inventory>).
  • @XmlElement(name = "item") — każdy element listy serializowany jest jako <item>.

Jeśli masz obiekty zagnieżdżone (np. Position), również trzeba je oznaczyć adnotacjami i dodać pusty konstruktor.

5. Praktyka: serializacja i deserializacja obiektu do 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; }

    // settery pominięto dla zwięzłości
}

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

    // settery pominięto
}

Serializacja:

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

Wynik 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>

Deserializacja działa analogicznie: JAXB sam odtworzy obiekty zagnieżdżone i kolekcje, jeśli klasy są poprawnie opisane.

6. Tabela: podstawowe adnotacje JAXB i ich efekt

Adnotacja Gdzie używać Co robi w XML
@XmlRootElement
Klasa Element korzeniowy
@XmlElement
Getter/pole Element wewnątrz XML
@XmlAttribute
Getter/pole Atrybut elementu
@XmlElementWrapper
Getter kolekcji „Opakowanie” kolekcji (np. <list>)
@XmlTransient
Pole/getter Wyklucza pole z serializacji
@XmlType
Klasa Steruje kolejnością elementów, nazwą typu

7. Cechy i ograniczenia JAXB

Kolejność elementów

Domyślnie JAXB może wypisywać elementy w porządku alfabetycznym. Aby jawnie ustawić kolejność, użyj @XmlType i właściwości propOrder:

@XmlType(propOrder = {"name", "level", "inventory", "position"})

Wykluczanie pól

Aby nie serializować pola/gettera, użyj @XmlTransient:

@XmlTransient
public String getSecretCode() { ... }

Problemy z kolekcjami

  • Nie używaj „surowych” kolekcji bez generyków: pisz List<Type>, a nie List.
  • Jeśli kolekcja przechowuje obiekty, ich klasy również muszą być oznaczone adnotacjami i mieć pusty konstruktor.

Błędy

  • Brak pustego konstruktora — otrzymasz JAXBException podczas unmarshallingu.
  • Nieoznaczona adnotacjami klasa zagnieżdżona — JAXB nie zdoła jej zserializować/deserializować.
  • Niestandardowe typy (np. LocalDate) wymagają adaptera (@XmlJavaTypeAdapter).

8. Typowe błędy podczas pracy z JAXB

Błąd nr 1: Brak pustego konstruktora. JAXB wymaga, aby serializowana klasa miała publiczny konstruktor bez parametrów. Jeśli go nie ma — przy unmarshallingu wystąpi wyjątek JAXBException.

Błąd nr 2: Nieoznaczone adnotacjami obiekty zagnieżdżone. Jeśli masz pole-obiekt, ale jego klasa nie jest oznaczona @XmlRootElement lub przynajmniej @XmlType, JAXB nie będzie w stanie poprawnie go zserializować/deserializować.

Błąd nr 3: Problemy z kolekcjami. JAXB nie rozumie „surowych” kolekcji bez wskazania typu elementów. Używaj generyków i poprawnie oznaczaj kolekcje (@XmlElementWrapper + @XmlElement).

Błąd nr 4: Niejawne sterowanie kolejnością elementów. Jeśli kolejność elementów w XML jest ważna dla integracji, użyj @XmlType z propOrder; w przeciwnym razie JAXB może wypisać elementy w innej kolejności (np. alfabetycznie).

Błąd nr 5: Użycie niestandardowych typów bez adaptera. JAXB nie potrafi serializować niektórych typów (np. LocalDate) bez adaptera. Zastosuj @XmlJavaTypeAdapter lub serializuj wartość jako łańcuch znaków.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION