1. Использование адаптеров (@XmlJavaTypeAdapter)
JAXB действительно похож на автоматическую коробку передач: пока всё стандартно — он работает идеально, но стоит встретить что-то необычное, и без ручного вмешательства уже не обойтись. Представьте, вы добавили в свой класс поле типа LocalDate или BigDecimal. JAXB растеряется — он просто не знает, как превратить их в XML и обратно. Или вы хотите, чтобы дата выглядела не как длинная строка 2024-06-01T00:00:00, а в привычном формате 01.06.2024. А может, у вас есть объект, который логичнее хранить в атрибуте, а не в элементе, или коллекция с вложенными объектами, требующая особого представления.
Все эти ситуации решаются через адаптеры. С их помощью можно подсказать JAXB, каким образом именно сериализовать и десериализовать сложные поля, задать нужный формат или даже пропустить ненужные данные. Это и есть ручное управление «передачами», которое даёт гибкость там, где автомат уже не справляется.
Что такое адаптер?
Адаптер — это специальный класс, который говорит JAXB: «Если встретишь вот такой тип, сериализуй его вот так, а десериализуй вот так». В Java адаптер реализует абстрактный класс javax.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>, где:
- ValueType — как данные будут представлены в XML (обычно это String, иногда Integer, Long, или даже другой объект).
- BoundType — реальный тип в вашем Java-классе (например, LocalDate).
Пример: сериализация поля типа LocalDate
import java.time.LocalDate;
import javax.xml.bind.annotation.*;
@XmlRootElement
public class Person {
private String name;
private LocalDate birthDate; // Вот тут проблема!
public Person() {} // JAXB требует публичный конструктор без параметров
public Person(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
@XmlElement
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@XmlElement
public LocalDate getBirthDate() { return birthDate; }
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
}
Если попытаться сериализовать такой объект, JAXB выбросит исключение:
javax.xml.bind.JAXBException: class java.time.LocalDate nor any of its super class is known to this context.
Шаг 1: Создаём адаптер
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// Адаптер для преобразования LocalDate <-> String
public class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy");
@Override
public LocalDate unmarshal(String v) throws Exception {
return (v == null || v.isEmpty()) ? null : LocalDate.parse(v, FORMATTER);
}
@Override
public String marshal(LocalDate v) throws Exception {
return (v == null) ? null : v.format(FORMATTER);
}
}
- marshal — преобразует объект Java (LocalDate) в строку для XML.
- unmarshal — преобразует строку из XML обратно в объект Java.
Шаг 2: Помечаем поле или геттер аннотацией
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
Или можно поставить аннотацию на само поле:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
Шаг 3: Проверяем результат
Теперь при сериализации объект будет выглядеть так:
<Person>
<name>Иван</name>
<birthDate>01.06.2024</birthDate>
</Person>
И обратно — при чтении из XML, строка "01.06.2024" превратится в объект LocalDate.
2. Применение адаптера к полю, геттеру или всему классу
Адаптер можно применять по-разному.
К отдельному полю или геттеру: Это самый частый случай.
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
К целому классу: Если вы хотите, чтобы JAXB всегда сериализовал некий тип через адаптер, можно пометить сам класс:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public class LocalDate { ... }
Обычно так делают для своих собственных классов, а не для стандартных (класс LocalDate модифицировать нельзя).
К коллекциям: Можно сериализовать, например, List<LocalDate> через адаптер, который превращает список дат в список строк.
3. Кастомизация имён элементов и атрибутов
Иногда требования к XML-структуре жёсткие: например, заказчик хочет, чтобы поле называлось не <birthDate>, а <birth_date>, или чтобы дата рождения была атрибутом, а не элементом.
Изменение имени элемента
@XmlElement(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
В XML теперь будет:
<birth_date>01.06.2024</birth_date>
Сериализация как атрибут
@XmlAttribute(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
В XML:
<Person birth_date="01.06.2024">
<name>Иван</name>
</Person>
Сочетание с адаптером
@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
4. Практические кейсы
Пропуск полей (@XmlTransient)
Иногда нужно, чтобы какое-то поле не попадало в XML вообще (например, внутренний идентификатор, пароль, временные данные).
@XmlTransient
private String internalCode;
Такое поле будет проигнорировано при сериализации и десериализации.
Форматирование чисел
Допустим, у вас есть поле с денежной суммой:
private BigDecimal balance;
JAXB не умеет сериализовать BigDecimal в нужном вам формате (например, с двумя знаками после запятой, через запятую). Пишем адаптер:
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.math.BigDecimal;
public class BigDecimalAdapter extends XmlAdapter<String, BigDecimal> {
@Override
public BigDecimal unmarshal(String v) throws Exception {
return (v == null || v.isEmpty()) ? null : new BigDecimal(v.replace(",", "."));
}
@Override
public String marshal(BigDecimal v) throws Exception {
return (v == null) ? null : String.format("%.2f", v);
}
}
И используем:
@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;
Вложенные структуры
Если у вас есть вложенные объекты, например:
public class Address {
private String city;
private String street;
// ...
}
JAXB сам сериализует вложенные объекты как элементы. Но если вам нужно, чтобы, например, city был атрибутом, а street — элементом, используйте аннотации:
public class Address {
@XmlAttribute
private String city;
@XmlElement
private String street;
}
5. Пример: Полная настройка сериализации с адаптером
Разовьём приложение: теперь у нас есть класс Person с датой рождения и балансом.
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.math.BigDecimal;
import java.time.LocalDate;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
@XmlElement
private String name;
@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
@XmlElement
@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;
@XmlTransient
private String password;
public Person() {}
public Person(String name, LocalDate birthDate, BigDecimal balance, String password) {
this.name = name;
this.birthDate = birthDate;
this.balance = balance;
this.password = password;
}
// геттеры и сеттеры...
}
Что мы получили:
- Имя сериализуется как элемент <name>.
- Дата рождения сериализуется как атрибут <Person birth_date="01.06.2024">.
- Баланс сериализуется как элемент <balance>1234.56</balance>.
- Пароль не попадает в XML вообще.
XML-файл:
<Person birth_date="01.06.2024">
<name>Иван</name>
<balance>1234.56</balance>
</Person>
6. Обработка коллекций и вложенных объектов
JAXB умеет работать с коллекциями, если они правильно аннотированы. Например, если у человека есть список адресов:
@XmlElementWrapper(name = "addresses")
@XmlElement(name = "address")
private List<Address> addresses;
В XML это будет выглядеть так:
<addresses>
<address city="Берлин">
<street>Александрплатц, 1</street>
</address>
<address city="Лимассол">
<street>Анексартисиас, 10</street>
</address>
</addresses>
Если тип в коллекции нестандартный (например, List<LocalDate>), можно применить адаптер к элементу коллекции:
@XmlElementWrapper(name = "dates")
@XmlElement(name = "date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private List<LocalDate> importantDates;
7. Пример: сериализация и десериализация с адаптером
Сериализация
Person person = new Person(
"Иван",
LocalDate.of(1990, 6, 1),
new BigDecimal("1234.56"),
"secretPassword"
);
JAXBContext context = JAXBContext.newInstance(Person.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(person, System.out); // Выведет XML в консоль
Десериализация
String xml = """
<Person birth_date="01.06.1990">
<name>Иван</name>
<balance>1234.56</balance>
</Person>
""";
JAXBContext context = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Person person = (Person) unmarshaller.unmarshal(new StringReader(xml));
System.out.println(person.getName() + " " + person.getBirthDate() + " " + person.getBalance());
8. Типичные ошибки при настройке сериализации и адаптеров
Ошибка №1: Отсутствие публичного конструктора без параметров. Если его нет, JAXB не сможет создать объект при десериализации и выбросит исключение.
Ошибка №2: Неправильное применение адаптера. Если поставить @XmlJavaTypeAdapter не на то поле или забыть геттер/сеттер, JAXB не будет знать, как сериализовать нужный тип.
Ошибка №3: Несовпадение формата при десериализации. Если в XML дата записана в формате, который не поддерживает ваш адаптер (например, "2024-06-01" вместо "01.06.2024"), метод unmarshal выбросит исключение.
Ошибка №4: Попытка сериализовать тип, который JAXB не поддерживает, без адаптера. Типичный пример — LocalDate, BigDecimal, Map, пользовательские сложные типы.
Ошибка №5: Игнорирование вложенных коллекций без аннотаций. Без @XmlElementWrapper коллекция сериализуется не так, как ожидается, или JAXB вообще не сможет корректно прочитать XML обратно.
Ошибка №6: Применение адаптера к коллекции не к элементу, а к списку. Если вы хотите сериализовать элементы списка через адаптер, ставьте аннотацию к элементу, а не к самой коллекции (например, @XmlJavaTypeAdapter над полем элемента, либо над полем списка с указанием типа элемента, как в примерах выше).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ