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 работает на основе аннотаций, которыми помечаются классы и их поля, чтобы управлять процессом сериализации/десериализации.
Главные аннотации
| Аннотация | Для чего нужна |
|---|---|
|
Обозначает корневой элемент XML (сам класс) |
|
Помечает поле/свойство как XML-элемент |
|
Помечает поле/свойство как XML-атрибут |
|
Управляет порядком элементов, именем типа и др. |
|
Исключает поле из сериализации |
Главные классы
- 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 |
|---|---|---|
|
Класс | Корневой элемент |
|
Геттер/поле | Элемент внутри XML |
|
Геттер/поле | Атрибут у элемента |
|
Геттер коллекции | «Обёртка» коллекции (например, <list>) |
|
Поле/геттер | Исключает поле из сериализации |
|
Класс | Управляет порядком элементов, именем типа |
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 или сериализуйте значение как строку.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ