JavaRush /Курси /JAVA 25 SELF /Робота з XML через JAXB: основи, анотації

Робота з XML через JAXB: основи, анотації

JAVA 25 SELF
Рівень 47 , Лекція 3
Відкрита

1. Знайомство з JAXB

JAXB (Java Architecture for XML Binding) — це стандартна технологія Java для перетворення (binding) Java-об’єктів у XML і назад. За допомогою JAXB можна легко серіалізувати об’єкти в XML-файли, а потім відтворювати їх із цих файлів.

JAXB входив до стандартної бібліотеки Java до версії 11 включно. Починаючи з Java 11, JAXB винесено в окремий модуль, який потрібно підключати через Maven/Gradle або завантажувати вручну. Для сучасних версій Java додайте залежності:

<!-- Приклад для 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>

Навіщо взагалі потрібен XML?

  • XML — універсальний, зрозумілий людині формат, який широко використовують для обміну даними між системами, конфігурування та зберігання інформації.
  • На відміну від бінарної серіалізації, XML легко прочитати, перевірити валідність за схемою та відкрити в браузері.

2. Основні класи й анотації JAXB

JAXB працює на основі анотацій, якими позначають класи та їхні поля, щоб керувати процесом серіалізації/десеріалізації.

Головні анотації

Анотація Для чого потрібна
@XmlRootElement
Позначає кореневий елемент XML (сам клас)
@XmlElement
Позначає поле/властивість як XML-елемент
@XmlAttribute
Позначає поле/властивість як XML-атрибут
@XmlType
Керує порядком елементів, ім’ям типу тощо
@XmlTransient
Виключає поле з серіалізації

Головні класи

  • JAXBContext — точка входу, створює контекст для серіалізації/десеріалізації конкретних класів.
  • Marshaller — перетворює об’єкт у XML (маршалінг, marshal()).
  • Unmarshaller — перетворює XML на об’єкт (анмаршалінг, unmarshal()).

3. Приклад: серіалізація об’єкта в XML

Створимо клас, який будемо серіалізувати. Нехай це буде персонаж для нашої гри:

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() {} // Обов’язковий порожній конструктор!

    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") — клас перетворюється на кореневий елемент <player>.
  • @XmlElement — поле буде окремим XML-елементом (<name>, <level>).
  • @XmlAttribute — поле буде атрибутом кореневого елемента (health="100").
  • Не забудьте про порожній конструктор! JAXB потребує його для десеріалізації.

Серіалізація об’єкта в 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); // Гарне форматування

        marshaller.marshal(player, System.out); // Виводимо XML у консоль
        // marshaller.marshal(player, new File("player.xml")); // Або у файл
    }
}

Результат:

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

Десеріалізація об’єкта з 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() + ", рівень: " + player.getLevel() + ", здоров’я: " + player.getHealth());
    }
}

4. Особливості та обмеження JAXB

Вимоги до класів

  • Публічний конструктор без параметрів — обов’язковий.
  • Для коректної роботи використовуйте гетери й сетери.
  • Усі поля, що підлягають серіалізації, мають бути доступними (через public API).
  • Вкладені об’єкти та колекції також мають бути серіалізованими (анотуйте їх і додайте порожній конструктор).

Робота з колекціями та вкладеними об’єктами

Припустімо, у гравця є інвентар (список предметів). Як серіалізувати колекцію?

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

@XmlRootElement(name = "player")
public class Player {
    // ... інші поля

    private List<String> inventory;

    public Player() {}

    // ... інші гетери/сетери

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

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

Результат серіалізації:

<player health="100">
    <name>Aragorn</name>
    <level>5</level>
    <inventory>
        <item>Sword</item>
        <item>Shield</item>
    </inventory>
</player>
  • @XmlElementWrapper — створює «обгортку» навколо колекції (елемент <inventory>).
  • @XmlElement(name = "item") — кожен елемент списку серіалізується як <item>.

Якщо у вас є вкладені об’єкти (наприклад, Position), їх також потрібно анотувати й додати порожній конструктор.

5. Практика: серіалізація та десеріалізація об’єкта в 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; }

    // сетери опущено для стислості
}

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

    // сетери опущено
}

Серіалізація:

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

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>

Десеріалізація працює аналогічно: JAXB сам розбере вкладені об’єкти та колекції, якщо класи описано коректно.

6. Таблиця: основні анотації JAXB та їхній ефект

Анотація Де використовувати Що робить у XML
@XmlRootElement
Клас Кореневий елемент
@XmlElement
Гетер/поле Елемент усередині XML
@XmlAttribute
Гетер/поле Атрибут елемента
@XmlElementWrapper
Гетер колекції «Обгортка» колекції (наприклад, <list>)
@XmlTransient
Поле/гетер Виключає поле з серіалізації
@XmlType
Клас Керує порядком елементів та ім’ям типу

7. Особливості та обмеження JAXB

Порядок елементів

За замовчуванням JAXB може виводити елементи в алфавітному порядку. Щоб явно задати порядок, використовуйте @XmlType і властивість propOrder:

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

Виключення полів

Щоб не серіалізувати поле/гетер, використовуйте @XmlTransient:

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

Проблеми з колекціями

  • Не використовуйте «сирі» колекції без дженериків: пишіть List<Type>, а не List.
  • Якщо колекція зберігає об’єкти, їхні класи також мають бути анотовані та мати порожній конструктор.

Помилки

  • Відсутній порожній конструктор — отримаєте JAXBException під час анмаршалінгу.
  • Неанотований вкладений клас — JAXB не зможе його серіалізувати/десеріалізувати.
  • Нестандартні типи (наприклад, LocalDate) потребують адаптера (@XmlJavaTypeAdapter).

8. Типові помилки під час роботи з JAXB

Помилка № 1: Відсутній порожній конструктор. JAXB вимагає, щоб у серіалізованого класу був публічний конструктор без параметрів. Якщо його немає — під час анмаршалінгу виникне виняток JAXBException.

Помилка № 2: Неанотовані вкладені об’єкти. Якщо у вас є поле-об’єкт, але його клас не анотовано @XmlRootElement або принаймні @XmlType, JAXB не зможе коректно його серіалізувати/десеріалізувати.

Помилка № 3: Проблеми з колекціями. JAXB не розуміє «сирі» колекції без указання типу елементів. Використовуйте дженерики та коректно анотуйте колекції (@XmlElementWrapper + @XmlElement).

Помилка № 4: Неявне керування порядком елементів. Якщо порядок елементів у XML важливий для інтеграції, використовуйте @XmlType з propOrder; інакше JAXB може вивести елементи в іншому порядку (наприклад, за абеткою).

Помилка № 5: Використання нестандартних типів без адаптера. JAXB не вміє серіалізувати деякі типи (наприклад, LocalDate) без адаптера. Застосовуйте @XmlJavaTypeAdapter або серіалізуйте значення як рядок.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ