CodeGym /행동 /JAVA 25 SELF /JAXB로 XML 다루기: 기본과 애너테이션

JAXB로 XML 다루기: 기본과 애너테이션

JAVA 25 SELF
레벨 47 , 레슨 3
사용 가능

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는 애너테이션 기반으로 동작하며, 클래스와 그 필드에 애너테이션을 지정해 직렬화/역직렬화 과정을 제어합니다.

주요 애너테이션

애너테이션 용도
@XmlRootElement
클래스를 XML 루트 요소로 지정
@XmlElement
필드/프로퍼티를 XML 요소로 표시
@XmlAttribute
필드/프로퍼티를 XML 속성으로 표시
@XmlType
요소 순서, 타입 이름 등을 제어
@XmlTransient
필드를 직렬화에서 제외

주요 클래스

  • 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에서의 효과
@XmlRootElement
클래스 루트 요소
@XmlElement
getter/필드 XML 내부의 요소
@XmlAttribute
getter/필드 요소의 속성
@XmlElementWrapper
컬렉션 getter 컬렉션 “래퍼”(예: <list>)
@XmlTransient
필드/getter 필드를 직렬화에서 제외
@XmlType
클래스 요소 순서와 타입 이름을 제어

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에서 요소 순서가 중요하다면 @XmlTypepropOrder를 사용하세요. 그렇지 않으면 JAXB가 다른 순서(예: 알파벳 순)로 출력할 수 있습니다.

오류 №5: 어댑터 없이 비표준 타입 사용. JAXB는 일부 타입(예: LocalDate)을 어댑터 없이 직렬화하지 못합니다. @XmlJavaTypeAdapter를 적용하거나 값을 문자열로 직렬화하세요.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION