CodeGym /Các khóa học /JAVA 25 SELF /Làm việc với XML bằng JAXB: cơ bản và annotation

Làm việc với XML bằng JAXB: cơ bản và annotation

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

1. Giới thiệu về JAXB

JAXB (Java Architecture for XML Binding) là công nghệ tiêu chuẩn của Java để chuyển đổi (binding) các đối tượng Java sang XML và ngược lại. Với JAXB, bạn có thể dễ dàng tuần tự hóa đối tượng thành các tệp XML, rồi tái tạo lại chúng từ các tệp đó.

JAXB từng nằm trong thư viện chuẩn của Java đến phiên bản 11 (bao gồm). Bắt đầu từ Java 11, JAXB được tách thành một mô-đun riêng, cần được thêm thông qua Maven/Gradle hoặc tải về thủ công. Với các phiên bản Java hiện đại, hãy thêm các phụ thuộc:

<!-- Ví dụ cho 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>

Tại sao cần XML?

  • XML là định dạng phổ biến, dễ đọc cho con người, được dùng rộng rãi để trao đổi dữ liệu giữa các hệ thống, cấu hình và lưu trữ thông tin.
  • Khác với tuần tự hóa nhị phân, XML dễ đọc bằng mắt, có thể kiểm tra tính hợp lệ theo schema và mở bằng trình duyệt.

2. Các lớp và annotation chính của JAXB

JAXB hoạt động dựa trên các annotation gắn vào lớp và các trường của nó để kiểm soát quá trình tuần tự hóa/giải tuần tự hóa.

Các annotation chính

Annotation Dùng để
@XmlRootElement
Đánh dấu phần tử gốc XML (bản thân lớp)
@XmlElement
Đánh dấu trường/thuộc tính là phần tử XML
@XmlAttribute
Đánh dấu trường/thuộc tính là thuộc tính XML
@XmlType
Điều khiển thứ tự phần tử, tên kiểu, v.v.
@XmlTransient
Loại trừ trường khỏi tuần tự hóa

Các lớp chính

  • JAXBContext — điểm vào; tạo context cho tuần tự hóa/giải tuần tự hóa các lớp cụ thể.
  • Marshaller — chuyển đối tượng thành XML (marshalling, marshal()).
  • Unmarshaller — chuyển XML thành đối tượng (unmarshalling, unmarshal()).

3. Ví dụ: tuần tự hóa đối tượng sang XML

Hãy tạo một lớp để tuần tự hóa. Giả sử đó là nhân vật cho trò chơi của chúng ta:

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() {} // Bắt buộc có constructor rỗng!

    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") — lớp trở thành phần tử gốc <player>.
  • @XmlElement — trường sẽ là phần tử XML riêng biệt (<name>, <level>).
  • @XmlAttribute — trường sẽ là thuộc tính của phần tử gốc (health="100").
  • Đừng quên constructor rỗng! JAXB yêu cầu nó cho việc giải tuần tự hóa.

Tuần tự hóa đối tượng sang 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); // Định dạng đẹp

        marshaller.marshal(player, System.out); // Ghi XML ra console
        // marshaller.marshal(player, new File("player.xml")); // Hoặc ghi ra tệp
    }
}

Kết quả:

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

Giải tuần tự hóa đối tượng từ 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() + ", cấp độ: " + player.getLevel() + ", sức khỏe: " + player.getHealth());
    }
}

4. Đặc điểm và hạn chế của JAXB

Yêu cầu đối với lớp

  • Constructor công khai không tham số — bắt buộc.
  • Để hoạt động đúng, hãy dùng getter và setter.
  • Mọi trường cần tuần tự hóa phải có thể truy cập (thông qua public API).
  • Các đối tượng lồng nhau và collection cũng phải có khả năng tuần tự hóa (thêm annotation và constructor rỗng).

Làm việc với collection và đối tượng lồng nhau

Giả sử người chơi có kho đồ (danh sách vật phẩm). Làm sao tuần tự hóa collection?

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

@XmlRootElement(name = "player")
public class Player {
    // ... các trường khác

    private List<String> inventory;

    public Player() {}

    // ... các getter/setter khác

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

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

Kết quả tuần tự hóa:

<player health="100">
    <name>Aragorn</name>
    <level>5</level>
    <inventory>
        <item>Sword</item>
        <item>Shield</item>
    </inventory>
</player>
  • @XmlElementWrapper — tạo “vỏ bọc” quanh collection (phần tử <inventory>).
  • @XmlElement(name = "item") — mỗi phần tử của danh sách được tuần tự hóa thành <item>.

Nếu bạn có các đối tượng lồng nhau (ví dụ Position), bạn cũng cần thêm annotation và constructor rỗng.

5. Thực hành: tuần tự hóa và giải tuần tự hóa đối tượng sang 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; }

    // bỏ qua setter cho ngắn gọn
}

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

    // bỏ qua setter
}

Tuần tự hóa:

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

Kết quả 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>

Giải tuần tự hóa hoạt động tương tự: JAXB sẽ tự xử lý các đối tượng lồng nhau và collection nếu các lớp được mô tả đúng.

6. Bảng: các annotation chính của JAXB và tác dụng của chúng

Annotation Sử dụng ở đâu Tác dụng trong XML
@XmlRootElement
Lớp Phần tử gốc
@XmlElement
Getter/trường Phần tử bên trong XML
@XmlAttribute
Getter/trường Thuộc tính của phần tử
@XmlElementWrapper
Getter của collection “Vỏ bọc” cho collection (ví dụ, <list>)
@XmlTransient
Trường/getter Loại trừ trường khỏi tuần tự hóa
@XmlType
Lớp Điều khiển thứ tự phần tử, tên kiểu

7. Đặc điểm và giới hạn của JAXB

Thứ tự phần tử

Theo mặc định, JAXB có thể xuất các phần tử theo thứ tự bảng chữ cái. Để chỉ định rõ ràng thứ tự, hãy dùng @XmlType và thuộc tính propOrder:

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

Loại trừ trường

Để không tuần tự hóa một trường/getter, hãy dùng @XmlTransient:

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

Vấn đề với collection

  • Không dùng collection “thô” không có generics: hãy viết List<Type>, không phải List.
  • Nếu collection lưu các đối tượng, lớp của chúng cũng phải được annotate và có constructor rỗng.

Lỗi

  • Thiếu constructor rỗng — sẽ gặp JAXBException khi unmarshalling.
  • Lớp lồng nhau không được annotate — JAXB sẽ không thể tuần tự hóa/giải tuần tự hóa nó.
  • Các kiểu không chuẩn (ví dụ LocalDate) cần adapter (@XmlJavaTypeAdapter).

8. Các lỗi thường gặp khi làm việc với JAXB

Lỗi №1: Thiếu constructor rỗng. JAXB yêu cầu lớp cần tuần tự hóa phải có constructor công khai không tham số. Nếu không có — khi unmarshalling sẽ phát sinh ngoại lệ JAXBException.

Lỗi №2: Đối tượng lồng nhau không có annotation. Nếu bạn có một trường là đối tượng, nhưng lớp của nó không được annotate bằng @XmlRootElement hoặc ít nhất @XmlType, JAXB sẽ không thể tuần tự hóa/giải tuần tự hóa chính xác.

Lỗi №3: Vấn đề với collection. JAXB không hiểu các collection “thô” nếu không chỉ rõ kiểu phần tử. Hãy dùng generics và annotate collection (@XmlElementWrapper + @XmlElement).

Lỗi №4: Không kiểm soát rõ ràng thứ tự phần tử. Nếu thứ tự phần tử trong XML quan trọng cho việc tích hợp, hãy dùng @XmlType với propOrder; nếu không, JAXB có thể xuất các phần tử theo thứ tự khác (ví dụ theo bảng chữ cái).

Lỗi №5: Dùng kiểu không chuẩn mà không có adapter. JAXB không thể tuần tự hóa một số kiểu (ví dụ LocalDate) nếu không có adapter. Hãy áp dụng @XmlJavaTypeAdapter hoặc tuần tự hóa giá trị dưới dạng chuỗi.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION