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 để |
|---|---|
|
Đánh dấu phần tử gốc XML (bản thân lớp) |
|
Đánh dấu trường/thuộc tính là phần tử XML |
|
Đánh dấu trường/thuộc tính là thuộc tính XML |
|
Điều khiển thứ tự phần tử, tên kiểu, v.v. |
|
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 |
|---|---|---|
|
Lớp | Phần tử gốc |
|
Getter/trường | Phần tử bên trong XML |
|
Getter/trường | Thuộc tính của phần tử |
|
Getter của collection | “Vỏ bọc” cho collection (ví dụ, <list>) |
|
Trường/getter | Loại trừ trường khỏi tuần tự hóa |
|
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.
GO TO FULL VERSION