CodeGym /Các khóa học /JAVA 25 SELF /Cấu hình tuần tự hóa XML: adapter tùy chỉnh

Cấu hình tuần tự hóa XML: adapter tùy chỉnh

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Sử dụng adapter (@XmlJavaTypeAdapter)

JAXB thật sự giống như hộp số tự động: khi mọi thứ đều chuẩn — nó chạy hoàn hảo, nhưng chỉ cần gặp điều gì đó bất thường thì sẽ cần can thiệp thủ công. Hãy tưởng tượng bạn thêm vào lớp của mình một trường kiểu LocalDate hoặc BigDecimal. JAXB sẽ lúng túng — nó đơn giản không biết cách biến chúng thành XML và ngược lại. Hoặc bạn muốn ngày tháng không trông như chuỗi dài 2024-06-01T00:00:00, mà ở định dạng quen thuộc 01.06.2024. Cũng có thể bạn có một đối tượng hợp lý hơn nếu lưu trong thuộc tính thay vì phần tử, hoặc một collection chứa đối tượng lồng nhau cần cách biểu diễn đặc biệt.

Tất cả các tình huống này được giải quyết bằng adapter. Với chúng, bạn có thể chỉ cho JAXB chính xác cách tuần tự hóa và giải tuần tự các trường phức tạp, đặt định dạng mong muốn hoặc thậm chí bỏ qua dữ liệu không cần thiết. Đó là “lái số tay” cho bạn sự linh hoạt ở nơi hộp số tự động không còn xử lý tốt.

Adapter là gì?

Adapter là một lớp đặc biệt nói với JAXB: “Nếu gặp kiểu này, hãy tuần tự hóa như thế này và giải tuần tự như thế kia”. Trong Java, adapter hiện thực lớp trừu tượng javax.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>, trong đó:

  • ValueType — cách dữ liệu sẽ được biểu diễn trong XML (thường là String, đôi khi là Integer, Long hoặc thậm chí là một đối tượng khác).
  • BoundType — kiểu thực trong lớp Java của bạn (ví dụ LocalDate).

Ví dụ: tuần tự hóa trường kiểu LocalDate

import java.time.LocalDate;
import javax.xml.bind.annotation.*;

@XmlRootElement
public class Person {
    private String name;
    private LocalDate birthDate; // Vấn đề nằm ở đây!

    public Person() {} // JAXB yêu cầu constructor công khai không tham số

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

Nếu cố gắng tuần tự hóa đối tượng như vậy, JAXB sẽ ném ngoại lệ:

javax.xml.bind.JAXBException: class java.time.LocalDate nor any of its super class is known to this context.

Bước 1: Tạo adapter

import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

// Adapter chuyển đổi 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 — chuyển đối tượng Java (LocalDate) thành chuỗi cho XML.
  • unmarshal — chuyển chuỗi từ XML về lại đối tượng Java.

Bước 2: Gắn chú thích lên trường hoặc getter

@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }

Hoặc có thể đặt chú thích ngay trên trường:

@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;

Bước 3: Kiểm tra kết quả

Bây giờ khi tuần tự hóa, đối tượng sẽ trông như sau:

<Person>
    <name>Ivan</name>
    <birthDate>01.06.2024</birthDate>
</Person>

Và ngược lại — khi đọc từ XML, chuỗi "01.06.2024" sẽ biến thành đối tượng LocalDate.

2. Áp dụng adapter cho trường, getter hoặc cả lớp

Adapter có thể được áp dụng theo nhiều cách.

Cho từng trường hoặc getter: Đây là trường hợp phổ biến nhất.

@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;

Cho cả lớp: Nếu bạn muốn JAXB luôn tuần tự hóa một kiểu nào đó qua adapter, có thể gắn chú thích lên chính lớp đó:

@XmlJavaTypeAdapter(LocalDateAdapter.class)
public class LocalDate { ... }

Thông thường cách này áp dụng cho lớp tự định nghĩa, không phải lớp chuẩn (không thể sửa lớp LocalDate).

Cho collection: Có thể tuần tự hóa, ví dụ, List<LocalDate> qua adapter chuyển danh sách ngày thành danh sách chuỗi.

3. Tùy biến tên phần tử và thuộc tính

Đôi khi yêu cầu về cấu trúc XML rất chặt: ví dụ, khách hàng muốn trường không phải là <birthDate>, mà là <birth_date>, hoặc muốn ngày sinh là thuộc tính chứ không phải phần tử.

Đổi tên phần tử

@XmlElement(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }

Trong XML sẽ là:

<birth_date>01.06.2024</birth_date>

Tuần tự hóa như thuộc tính

@XmlAttribute(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }

Trong XML:

<Person birth_date="01.06.2024">
    <name>Ivan</name>
</Person>

Kết hợp với adapter

@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }

4. Tình huống thực tế

Bỏ qua trường (@XmlTransient)

Đôi khi cần một trường không xuất hiện trong XML (ví dụ, mã nội bộ, mật khẩu, dữ liệu tạm).

@XmlTransient
private String internalCode;

Trường như vậy sẽ bị bỏ qua khi tuần tự hóa và giải tuần tự.

Định dạng số

Giả sử bạn có trường số tiền:

private BigDecimal balance;

JAXB không biết tuần tự hóa BigDecimal theo định dạng bạn cần (ví dụ có hai chữ số thập phân, dùng dấu phẩy). Hãy viết 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);
    }
}

Và sử dụng:

@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;

Cấu trúc lồng nhau

Nếu bạn có các đối tượng lồng nhau, ví dụ:

public class Address {
    private String city;
    private String street;
    // ...
}

JAXB tự tuần tự hóa đối tượng lồng nhau như các phần tử. Nhưng nếu bạn cần, chẳng hạn, city là thuộc tính, còn street là phần tử, hãy dùng chú thích:

public class Address {
    @XmlAttribute
    private String city;
    @XmlElement
    private String street;
}

5. Ví dụ: Cấu hình tuần tự hóa đầy đủ với adapter

Mở rộng ứng dụng: giờ ta có lớp Person với ngày sinh và số dư.

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

    // getters và setters...
}

Chúng ta có gì:

  • Tên được tuần tự hóa như phần tử <name>.
  • Ngày sinh được tuần tự hóa như thuộc tính <Person birth_date="01.06.2024">.
  • Số dư được tuần tự hóa như phần tử <balance>1234.56</balance>.
  • Mật khẩu không xuất hiện trong XML.

Tệp XML:

<Person birth_date="01.06.2024">
    <name>Ivan</name>
    <balance>1234.56</balance>
</Person>

6. Xử lý collection và đối tượng lồng nhau

JAXB có thể làm việc với collection nếu được chú thích đúng. Ví dụ, nếu một người có danh sách địa chỉ:

@XmlElementWrapper(name = "addresses")
@XmlElement(name = "address")
private List<Address> addresses;

Trong XML sẽ trông như sau:

<addresses>
    <address city="Berlin">
        <street>Alexanderplatz, 1</street>
    </address>
    <address city="Limassol">
        <street>Anexartisias, 10</street>
    </address>
</addresses>

Nếu kiểu trong collection là không chuẩn (ví dụ, List<LocalDate>), có thể áp dụng adapter cho phần tử của collection:

@XmlElementWrapper(name = "dates")
@XmlElement(name = "date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private List<LocalDate> importantDates;

7. Ví dụ: tuần tự hóa và giải tuần tự với adapter

Tuần tự hóa

Person person = new Person(
    "Ivan",
    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); // In XML ra console

Giải tuần tự

String xml = """
    <Person birth_date="01.06.1990">
        <name>Ivan</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. Lỗi thường gặp khi cấu hình tuần tự hóa và adapter

Lỗi 1: Thiếu constructor công khai không tham số. Nếu thiếu, JAXB sẽ không thể tạo đối tượng khi giải tuần tự và ném ngoại lệ.

Lỗi 2: Áp dụng adapter không đúng. Nếu đặt @XmlJavaTypeAdapter không đúng trường hoặc quên getter/setter, JAXB sẽ không biết cách tuần tự hóa kiểu cần thiết.

Lỗi 3: Không khớp định dạng khi giải tuần tự. Nếu trong XML ngày ở định dạng mà adapter của bạn không hỗ trợ (ví dụ, "2024-06-01" thay vì "01.06.2024"), phương thức unmarshal sẽ ném ngoại lệ.

Lỗi 4: Cố tuần tự hóa kiểu mà JAXB không hỗ trợ, nhưng không dùng adapter. Ví dụ điển hình — LocalDate, BigDecimal, Map, các kiểu phức tạp tự định nghĩa.

Lỗi 5: Bỏ qua collection lồng nhau nếu không có chú thích. Thiếu @XmlElementWrapper có thể khiến collection được tuần tự hóa không như mong đợi hoặc JAXB không đọc lại XML đúng cách.

Lỗi 6: Áp dụng adapter cho collection ở cấp danh sách, không phải phần tử. Nếu muốn tuần tự hóa các phần tử của danh sách qua adapter, hãy đặt chú thích ở phần tử, không phải chính collection (ví dụ, @XmlJavaTypeAdapter trên trường phần tử, hoặc trên trường danh sách với chỉ định kiểu phần tử như trong ví dụ trên).

1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Tuần tự hóa XML
Tuần tự hóa XML
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION