1. JAXB 소개
JAXB (Java Architecture for XML Binding)는 Java 객체를 XML로, 그리고 그 반대로 변환(binding)하는 표준 Java 기술입니다. JAXB를 사용하면 객체를 XML 파일로 손쉽게 직렬화하고, 그 파일에서 다시 객체를 복원할 수 있습니다.
JAXB는 11 버전까지 표준 Java 라이브러리에 포함되어 있었습니다. Java 11부터는 별도 모듈로 분리되어 Maven/Gradle로 추가하거나 수동으로 다운로드해야 합니다. 최신 Java 버전에서는 다음 의존성을 추가하세요:
<!-- 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>
XML은 왜 필요할까요?
- XML은 범용적이고 사람이 읽을 수 있는 형식으로, 시스템 간 데이터 교환, 구성, 정보 저장에 널리 사용됩니다.
- 이진 직렬화와 달리, XML은 눈으로 쉽게 읽고, 스키마로 유효성 검사하며, 브라우저로 열어볼 수 있습니다.
2. JAXB의 핵심 클래스와 애너테이션
JAXB는 애너테이션 기반으로 동작하며, 클래스와 그 필드에 애너테이션을 지정해 직렬화/역직렬화 과정을 제어합니다.
주요 애너테이션
| 애너테이션 | 용도 |
|---|---|
|
클래스를 XML 루트 요소로 지정 |
|
필드/프로퍼티를 XML 요소로 표시 |
|
필드/프로퍼티를 XML 속성으로 표시 |
|
요소 순서, 타입 이름 등을 제어 |
|
필드를 직렬화에서 제외 |
주요 클래스
- JAXBContext — 진입점으로, 특정 클래스의 직렬화/역직렬화 컨텍스트를 생성합니다.
- Marshaller — 객체를 XML로 변환(마샬링, marshal()).
- Unmarshaller — XML을 객체로 변환(언마샬링, unmarshal()).
3. 예제: 객체를 XML로 직렬화
직렬화할 클래스를 만들어봅니다. 여기서는 게임용 캐릭터를 예로 들겠습니다:
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() {} // 반드시 필요한 기본 생성자!
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") — 클래스가 루트 요소 <player>로 변환됩니다.
- @XmlElement — 필드는 별도의 XML 요소가 됩니다(<name>, <level>).
- @XmlAttribute — 필드는 루트 요소의 속성이 됩니다(health="100").
- 기본 생성자를 잊지 마세요! JAXB는 역직렬화를 위해 이를 요구합니다.
객체를 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); // 서식 있는 출력
marshaller.marshal(player, System.out); // XML을 콘솔에 출력
// marshaller.marshal(player, new File("player.xml")); // 또는 파일로
}
}
결과:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<player health="100">
<name>Aragorn</name>
<level>5</level>
</player>
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() + ", 레벨: " + player.getLevel() + ", 체력: " + player.getHealth());
}
}
4. JAXB의 특징과 제약
클래스 요구 사항
- 매개변수가 없는 public 기본 생성자 — 필수.
- 정상 동작을 위해 getter와 setter를 사용하세요.
- 직렬화되는 모든 필드는 접근 가능해야 합니다(public API).
- 중첩 객체와 컬렉션도 직렬화 가능해야 합니다(애너테이션을 추가하고 기본 생성자를 제공).
컬렉션과 중첩 객체 처리
예를 들어, 플레이어에 인벤토리(아이템 목록)가 있다고 합시다. 컬렉션은 어떻게 직렬화할까요?
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;
@XmlRootElement(name = "player")
public class Player {
// ... 나머지 필드
private List<String> inventory;
public Player() {}
// ... 나머지 getter/setter
@XmlElementWrapper(name = "inventory")
@XmlElement(name = "item")
public List<String> getInventory() {
return inventory;
}
public void setInventory(List<String> inventory) {
this.inventory = inventory;
}
}
직렬화 결과:
<player health="100">
<name>Aragorn</name>
<level>5</level>
<inventory>
<item>Sword</item>
<item>Shield</item>
</inventory>
</player>
- @XmlElementWrapper — 컬렉션을 감싸는 “래퍼” 요소를 생성합니다(요소 <inventory>).
- @XmlElement(name = "item") — 리스트의 각 항목이 <item> 요소로 직렬화됩니다.
중첩 객체(예: Position)가 있다면, 해당 클래스에도 애너테이션을 추가하고 기본 생성자를 제공해야 합니다.
5. 실습: 객체를 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; }
// 간략화를 위해 setter 생략
}
@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; }
// setter 생략
}
직렬화:
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);
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>
역직렬화도 동일하게 동작합니다. 클래스가 올바르게 정의되어 있다면 JAXB가 중첩 객체와 컬렉션을 자동으로 처리합니다.
6. 표: JAXB 주요 애너테이션과 효과
| 애너테이션 | 사용 위치 | XML에서의 효과 |
|---|---|---|
|
클래스 | 루트 요소 |
|
getter/필드 | XML 내부의 요소 |
|
getter/필드 | 요소의 속성 |
|
컬렉션 getter | 컬렉션 “래퍼”(예: <list>) |
|
필드/getter | 필드를 직렬화에서 제외 |
|
클래스 | 요소 순서와 타입 이름을 제어 |
7. JAXB의 특징과 제한
요소 순서
기본적으로 JAXB는 요소를 알파벳 순으로 출력할 수 있습니다. 순서를 명시하려면 @XmlType과 속성 propOrder를 사용하세요:
@XmlType(propOrder = {"name", "level", "inventory", "position"})
필드 제외
필드/게터를 직렬화하지 않으려면 @XmlTransient를 사용하세요:
@XmlTransient
public String getSecretCode() { ... }
컬렉션 관련 문제
- 제네릭이 없는 ‘raw’ 컬렉션을 사용하지 마세요: List<Type>을 사용하고 List만 쓰지 마세요.
- 컬렉션이 객체를 보관한다면, 해당 클래스들도 애너테이션이 있고 기본 생성자가 있어야 합니다.
오류
- 기본 생성자가 없음 — 언마샬링 시 JAXBException이 발생합니다.
- 애너테이션되지 않은 중첩 클래스 — JAXB가 해당 클래스를 직렬화/역직렬화할 수 없습니다.
- 비표준 타입(예: LocalDate)은 어댑터(@XmlJavaTypeAdapter)가 필요합니다.
8. JAXB 사용 시 흔한 오류
오류 №1: 기본 생성자가 없음. JAXB는 직렬화 대상 클래스에 public 기본 생성자가 있어야 합니다. 없으면 언마샬링 중 JAXBException이 발생합니다.
오류 №2: 애너테이션되지 않은 중첩 객체. 필드가 객체를 참조하는데 그 클래스가 @XmlRootElement 또는 최소한 @XmlType으로 애너테이션되지 않았다면, JAXB는 이를 올바르게 직렬화/역직렬화할 수 없습니다.
오류 №3: 컬렉션 처리 문제. 제네릭이 없는 ‘raw’ 컬렉션은 JAXB가 이해하지 못합니다. 제네릭을 사용하고 컬렉션을 올바르게 애너테이션하세요(@XmlElementWrapper + @XmlElement).
오류 №4: 요소 순서를 명시적으로 관리하지 않음. 통합을 위해 XML에서 요소 순서가 중요하다면 @XmlType과 propOrder를 사용하세요. 그렇지 않으면 JAXB가 다른 순서(예: 알파벳 순)로 출력할 수 있습니다.
오류 №5: 어댑터 없이 비표준 타입 사용. JAXB는 일부 타입(예: LocalDate)을 어댑터 없이 직렬화하지 못합니다. @XmlJavaTypeAdapter를 적용하거나 값을 문자열로 직렬화하세요.
GO TO FULL VERSION