1. Użycie adapterów (@XmlJavaTypeAdapter)
JAXB naprawdę przypomina automatyczną skrzynię biegów: dopóki wszystko jest standardowe — działa idealnie, lecz gdy pojawia się coś nietypowego, bez ręcznej ingerencji już się nie obejdzie. Wyobraź sobie, że dodałeś do swojej klasy pole typu LocalDate albo BigDecimal. JAXB się pogubi — po prostu nie wie, jak zamienić je na XML i z powrotem. Albo chcesz, aby data nie wyglądała jak długa fraza 2024-06-01T00:00:00, tylko w zwykłym formacie 01.06.2024. A może masz obiekt, który sensowniej przechowywać w atrybucie, a nie w elemencie, lub kolekcję ze zagnieżdżonymi obiektami, wymagającą szczególnej reprezentacji.
Wszystkie te sytuacje rozwiązuje się przez adaptery. Za ich pomocą możesz podpowiedzieć JAXB, w jaki sposób dokładnie serializować i deserializować złożone pola, narzucić pożądany format albo nawet pominąć niepotrzebne dane. To właśnie ręczne „przełączanie biegów”, które daje elastyczność tam, gdzie automat już nie wystarcza.
Co to jest adapter?
Adapter to specjalna klasa, która mówi JAXB: „Jeśli napotkasz taki typ, serializuj go tak, a deserializuj tak”. W Javie adapter realizuje klasę abstrakcyjną javax.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>, gdzie:
- ValueType — jak dane będą reprezentowane w XML (zwykle to String, czasem Integer, Long lub nawet inny obiekt).
- BoundType — rzeczywisty typ w twojej klasie Java (np. LocalDate).
Przykład: serializacja pola typu LocalDate
import java.time.LocalDate;
import javax.xml.bind.annotation.*;
@XmlRootElement
public class Person {
private String name;
private LocalDate birthDate; // Tu jest problem!
public Person() {} // JAXB wymaga publicznego konstruktora bezparametrowego
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; }
}
Jeśli spróbujesz zserializować taki obiekt, JAXB wyrzuci wyjątek:
javax.xml.bind.JAXBException: class java.time.LocalDate nor any of its super class is known to this context.
Krok 1: Tworzymy adapter
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// Adapter do konwersji 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 — konwertuje obiekt Javy (LocalDate) do łańcucha dla XML.
- unmarshal — konwertuje łańcuch z XML z powrotem do obiektu Javy.
Krok 2: Oznaczamy pole lub getter adnotacją
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
Można też umieścić adnotację bezpośrednio na polu:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
Krok 3: Sprawdzamy rezultat
Teraz przy serializacji obiekt będzie wyglądał tak:
<Person>
<name>Iwan</name>
<birthDate>01.06.2024</birthDate>
</Person>
I odwrotnie — przy odczycie z XML łańcuch "01.06.2024" zamieni się w obiekt LocalDate.
2. Zastosowanie adaptera do pola, gettera lub całej klasy
Adapter można stosować na różne sposoby.
Do pojedynczego pola lub gettera: To najczęstszy przypadek.
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;
Do całej klasy: Jeśli chcesz, aby JAXB zawsze serializował jakiś typ przez adapter, możesz oznaczyć samą klasę:
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public class LocalDate { ... }
Zwykle robi się tak dla własnych klas, a nie dla standardowych (klasy LocalDate nie da się modyfikować).
Dla kolekcji: Można serializować np. List<LocalDate> przez adapter, który zamienia listę dat na listę łańcuchów.
3. Dostosowanie nazw elementów i atrybutów
Czasem wymagania wobec struktury XML są sztywne: np. klient chce, aby pole nazywało się nie <birthDate>, lecz <birth_date>, albo by data urodzenia była atrybutem zamiast elementu.
Zmiana nazwy elementu
@XmlElement(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
W XML będzie teraz:
<birth_date>01.06.2024</birth_date>
Serializacja jako atrybut
@XmlAttribute(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }
W XML:
<Person birth_date="01.06.2024">
<name>Iwan</name>
</Person>
Połączenie z adapterem
@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }
4. Przypadki praktyczne
Pomijanie pól (@XmlTransient)
Czasem trzeba, aby jakieś pole w ogóle nie trafiało do XML (np. wewnętrzny identyfikator, hasło, dane tymczasowe).
@XmlTransient
private String internalCode;
Takie pole zostanie zignorowane przy serializacji i deserializacji.
Formatowanie liczb
Załóżmy, że masz pole z kwotą pieniędzy:
private BigDecimal balance;
JAXB nie potrafi serializować BigDecimal w potrzebnym ci formacie (np. z dwoma miejscami po przecinku, z przecinkiem). Piszemy adapter:
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);
}
}
I używamy:
@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;
Struktury zagnieżdżone
Jeśli masz obiekty zagnieżdżone, na przykład:
public class Address {
private String city;
private String street;
// ...
}
JAXB sam zserializuje zagnieżdżone obiekty jako elementy. Ale jeśli potrzebujesz, by np. city był atrybutem, a street — elementem, użyj adnotacji:
public class Address {
@XmlAttribute
private String city;
@XmlElement
private String street;
}
5. Przykład: pełna konfiguracja serializacji z adapterem
Rozwińmy przykład: mamy teraz klasę Person z datą urodzenia i saldem.
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;
}
// gettery i settery...
}
Co uzyskaliśmy:
- Imię serializuje się jako element <name>.
- Data urodzenia serializuje się jako atrybut <Person birth_date="01.06.2024">.
- Saldo serializuje się jako element <balance>1234.56</balance>.
- Hasło w ogóle nie trafia do XML.
Plik XML:
<Person birth_date="01.06.2024">
<name>Iwan</name>
<balance>1234.56</balance>
</Person>
6. Obsługa kolekcji i obiektów zagnieżdżonych
JAXB potrafi pracować z kolekcjami, jeśli są poprawnie oznaczone adnotacjami. Na przykład, jeśli osoba ma listę adresów:
@XmlElementWrapper(name = "addresses")
@XmlElement(name = "address")
private List<Address> addresses;
W XML będzie to wyglądało tak:
<addresses>
<address city="Berlin">
<street>Alexanderplatz, 1</street>
</address>
<address city="Limassol">
<street>Anexartisias, 10</street>
</address>
</addresses>
Jeśli typ w kolekcji jest niestandardowy (np. List<LocalDate>), można zastosować adapter do elementu kolekcji:
@XmlElementWrapper(name = "dates")
@XmlElement(name = "date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private List<LocalDate> importantDates;
7. Przykład: serializacja i deserializacja z adapterem
Serializacja
Person person = new Person(
"Iwan",
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); // Wypisze XML na konsolę
Deserializacja
String xml = """
<Person birth_date="01.06.1990">
<name>Iwan</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. Typowe błędy przy konfiguracji serializacji i adapterów
Błąd nr 1: Brak publicznego konstruktora bezparametrowego. Jeśli go nie ma, JAXB nie będzie w stanie utworzyć obiektu przy deserializacji i wyrzuci wyjątek.
Błąd nr 2: Nieprawidłowe zastosowanie adaptera. Jeśli umieścisz @XmlJavaTypeAdapter nie na tym polu albo zapomnisz o getterze/setterze, JAXB nie będzie wiedział, jak serializować dany typ.
Błąd nr 3: Niezgodny format przy deserializacji. Jeśli w XML data jest zapisana w formacie, którego twój adapter nie obsługuje (np. "2024-06-01" zamiast "01.06.2024"), metoda unmarshal wyrzuci wyjątek.
Błąd nr 4: Próba serializacji typu, którego JAXB nie obsługuje, bez adaptera. Typowy przykład — LocalDate, BigDecimal, Map, własne złożone typy.
Błąd nr 5: Ignorowanie zagnieżdżonych kolekcji bez adnotacji. Bez @XmlElementWrapper kolekcja może zserializować się inaczej, niż oczekujesz, lub JAXB w ogóle nie będzie w stanie poprawnie odczytać XML z powrotem.
Błąd nr 6: Zastosowanie adaptera do kolekcji zamiast do elementu. Jeśli chcesz serializować elementy listy przez adapter, umieszczaj adnotację przy elemencie, a nie przy samej kolekcji (np. @XmlJavaTypeAdapter nad polem elementu lub nad polem listy z określeniem typu elementu, jak w przykładach powyżej).
GO TO FULL VERSION