이 팁에서는 세부적인 XML 처리 과정을 자세히 알아보지 않고도 자바 SE 6에서 JAXB(Java Architecture for XML Binding)를 사용하여 시스템 간에 XML 데이터를 교환하는 방법에 대해 알아보겠습니다. 여러 중요한 개선 사항 중에서도 JAXB 2.0은 주석을 사용하여 자바 개체를 XML에 바인딩하는 작업을 지원합니다. 이 기능을 사용하면 단순히 기존 개체 모델에 주석을 추가하는 방법으로 애플리케이션에서 XML 데이터를 생성할 수 있습니다.

JAXB란?

JAXB는 XML 사용 시 낮은 수준의 정보에서 사용자와 사용자의 코드를 숨겨 자바 애플리케이션의 XML 데이터를 간단히 사용할 수 있도록 합니다. JAXB를 사용하면 XML과 자바 간에 쉽게 앞뒤로 이동할 수 있습니다. JAXB 구현은 XML 스키마를 받아 해당 스키마에 매핑되는 자바 클래스를 생성하는 스키마 컴파일러를 제공합니다. XML 문서의 데이터는 JAXB의 바인딩 런타임 프레임워크를 통해, 스키마 컴파일러가 생성한 클래스에 자동으로 바인딩될 수 있습니다. 이 작업을 언마샬링(Unmarshalling)이라고 합니다. 언마샬링되면 필요에 따라 콘텐츠를 Java로 조작하거나 수정할 수 있습니다. JAXB는 자바 개체에서 XML 인스턴스 문서로 데이터를 쓸(마샬링할) 수도 있습니다. JAXB는 경우에 따라 이러한 작업의 일부로 콘텐츠에 대한 검증을 수행합니다.

스키마 컴파일러뿐 아니라 JAXB 구현도 자바 클래스의 주석을 보고 해당 XML 스키마를 생성하는 스키마 생성기를 제공합니다. 이 기능은 JAXB 2.0의 새로운 기능입니다.

샘플 애플리케이션

동물 병원에서 고객들에게 다음 애완동물 검진 예약 일정을 알려주는 고지문을 보내려고 합니다. 또한 애완동물의 생일때 애완동물의 주인에게 생일 축하 카드를 보내려고 합니다. NiceVet 동물 병원은 생일 카드를 인쇄하고 발송하기 위해 서비스 공급업체인 WePrintStuff와 계약을 맺습니다.

NiceVet에는 이미 NiceVet의 환자로 등록된 동물과 그 주인에 관한 정보를 관리하는 애플리케이션이 있습니다. 다음은 NiceVet의 기존 개체 모델 다이어그램입니다.

NiceVet은 현재 사용 중인 애플리케이션을 수정하는 데 많은 시간과 비용을 들이지 않고 필요한 알림 데이터를 처리할 수 있도록 WePrintStuff에 보내야 합니다.

XML 생성

인쇄 및 우편 발송 서비스를 위해 몇 가지 XML 데이터를 생성할 수 있도록 개체 모델을 주석 처리할 차례입니다. NiceVet의 개체 모델을 보면 PrintOrder 클래스에 앞으로 있을 이벤트에 관한 정보가 있음을 알 수 있습니다. 따라서 이 정보를 서비스 공급업체에 보낼 데이터의 루트 요소로 설정하는 것이 좋습니다. @XmlRootElement 주석을 PrintOrder 클래스에 추가해보겠습니다. 그러면 생성된 XML에서 printOrder가 루트 요소가 됩니다.


import javax.xml.bind.annotation.XmlRootElement;

// the root element of our generated XML
@XmlRootElement(name = "printOrder")
public class PrintOrder {

private long id;
private List<Event> events;

주석을 좀 더 추가해 보겠습니다.

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

// the root element of our generated XML
@XmlRootElement(name = "printOrder")
// maps this class to a generated schema type
@XmlType(name = "PrintOrderType", propOrder = {"events"})
// bind all non-static, non-transient fields
// to XML unless annotated with @XmlTransient
@XmlAccessorType(XmlAccessType.FIELD)
public class PrintOrder {

// maps this field to an XML attribute
@XmlAttribute(required = true)
private long id;
// custom adapter maps this field to a type,
// NotificationsType, which makes it easy to
// generate the desired XML
@XmlJavaTypeAdapter(value=EventAdapter.class)
@XmlElement(name = "notifications")
private List<Event> events;

@XmlTypePrintOrder 클래스를 PrintOrderType이라는 이름으로 생성된 스키마 유형에 매핑합니다. @XmlAccessorType(XmlAccessType.FIELD) 주석이 있으면 @XmlTransient로 주석을 추가하지 않은 경우 JAXB에서 정적, 임시 필드가 아닌 모든 필드를 XML에 바인딩합니다. XmlAccessType.FIELD 설정이 있으면 명시적으로 주석 처리하지 않는 한 getter/setter 쌍이 XML에 바인딩되지 않습니다. 기본 설정은 XmlAccessType.PUBLIC_MEMBER(주석 처리를 다르게 하지 않으면 모든 공개 필드 및 getter/setter 쌍이 바인딩됨)이며 다른 설정은 XmlAccessType.PROPERTYXmlAccessType.NONE입니다.

코드 목록을 아래로 이동하면 PrintOrderType 요소에서 필드 ID가 XML 속성에 매핑되는 것을 알 수 있습니다. 인쇄 순서에도 일종의 ID가 필요하며 그에 따라 ID 필드를 주석 처리했습니다.

PrintOrder 클래스에는 예약 일정과 생일이 모두 포함된 이벤트 목록이 있습니다. @XmlJavaTypeAdapter 주석은 사용자 지정 어댑터인 EventAdapter를 사용할 것임을 JAXB에 알려 이 자바 구성을 XML에 매핑하도록 합니다. 이 주석을 지원하려면 XmlAdapter를 확장하고 추상 마샬 및 언마샬 메소드를 덮어쓰는 어댑터 클래스를 작성해야 합니다.


public class EventAdapter extends XmlAdapter<NotificationsType, List<Event>> {

// adapt original Java construct to a type, NotificationsType,
// which we can easily map to the XML output we want
public NotificationsType marshal(List<Event> events) throws Exception {
List<Appointment> appointments = new ArrayList<Appointment>();
List<Birthday> birthdays = new ArrayList<Birthday>();

for (Event e : events) {
if (e instanceof Appointment) {
appointments.add((Appointment)e);
} else {
birthdays.add((Birthday)e);
}
}
return new NotificationsType(appointments, birthdays);
}

// reverse operation: map XML type to Java
public List<Event> unmarshal(NotificationsType notifications) throws Exception { ... }

EventAdapter의 마샬 메소드는 원래 자바 구성인 List<Event> 이벤트를 가져와 원하는 XML 출력에 더 쉽게 매핑할 수 있는 형식으로 변환합니다. NotificationsType을 살펴보겠습니다.

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

public class NotificationsType {

// produce a wrapper XML element around this collection
@XmlElementWrapper(name = "appointments")
// maps each member of this list to an XML element named appointment
@XmlElement(name = "appointment")
//private List<Appointment> appointments;
private List<Appointment> appointments;
// produce a wrapper XML element around this collection
@XmlElementWrapper(name = "birthdays")
// maps each member of this list to an XML element named birthday
@XmlElement(name = "birthday")
//private List<Birthday> birthdays;
private List<Birthday> birthdays;

public NotificationsType() {}

public NotificationsType(List<Appointment> appointments, List<Birthday> birthdays) {
this.appointments = appointments;
this.birthdays = birthdays;
}

@XmlElementWrapper 주석은 각 컬렉션 주변에 래퍼 XML 요소를 만들고 @XmlElement 주석은 컬렉션의 각 구성원을 해당 유형의 XML 요소에 매핑합니다. 마지막으로 사용할 PrintOrder.events@XmlElement(name = "notifications") 주석과 결합하면 다음과 같은 형태가 됩니다.

<printOrder>
<notifications>
<appointments>
<appointment>
...
</appointment>
</appointments>
<birthdays>
<birthday>
...
</birthday>
</birthdays>
</notifications>
</printOrder>

언급할만한 다른 주석은 추상 기본 클래스인 Event(아래 표시)에 있습니다. 이 클래스에서 하위 클래스인 Appointment와 Birthday가 상속됩니다. 서비스 공급업체인 WePrintStuff에는 이 상속 계층이 별로 필요하지 않으므로 Event@XmlTransient 주석을 사용하여 제거하겠습니다. 애완동물 주인 필드와 애완동물 필드는 여전히 필요하므로 @XmlElement(required = true)로 주석 처리합니다.

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;

// no need to capture this inheritance hierarchy in XML
@XmlTransient
public abstract class Event {

@XmlElement(required = true)
private Owner owner;
@XmlElement(required = true)
private Pet pet;

public Event() {}

public Event(Owner owner, Pet pet) {
this.owner = owner;
this.pet = pet;
}

Birthday 및 Appointment 유형의 개체가 XML로 마샬링되면 하위 유형인 Event로부터 관련 데이터를 모두 포함하게 됩니다. 아래에서 Birthday 클래스를 보십시오.

@XmlType(name = "BirthdayType", propOrder = {"age", "birthday", "owner", "pet"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Birthday extends Event {

@XmlElement(required = true)
private int age;

public Birthday() {}

public Birthday(Owner owner, Pet pet) {
super(owner, pet);
this.age = this.calculateAge();
}

@XmlElement(name = "birthday", required = true)
public Date getBirthday() {
return this.getPet().getDateOfBirth();
}

@XmlType.propOrder는 콘텐츠가 마샬링되고 언마샬링되는 순서를 지정합니다. 그렇지 않은 경우 자바 리플렉션의 특성 때문에 이 순서가 지정되지 않습니다. 다른 방법으로 @XmlAccessorOrder 주석을 사용하여 순서를 지정할 수도 있습니다. 자바 튜토리얼에 설명된 것처럼 순서를 지정하면 JAXB 구현 전체에서 애플리케이션 이식성이 향상됩니다.

dateOfBirth 필드가 실제로는 Pet 클래스에 있지만 이 데이터가 birthday 요소의 하위 요소로 구성된다면 XML 표현이 더 자연스러워집니다. getBirthday() 메소드에 주석을 추가하여 Pet에 위임하면 Birthday 클래스에 dateOfBirth 필드가 없어도 이와 같은 효과를 만들 수 있습니다.

따라서 WePrintStuff에서는 NiceVet에서 보낸 데이터에 추가 처리 작업을 수행할 필요가 없으며, 올바른 알림 템플릿을 찾아서 해당 정보를 인쇄만 하면 됩니다. PrintOrder 클래스의 이벤트 목록에서 본 것처럼 원하는 XML 출력을 얻기 위해 사용자 지정 어댑터를 제공할 수 있습니다. 그러나 이번에는 패키지 수준의 주석을 통해 어댑터를 지정하겠습니다. 이 어댑터는 모든 java.util.Date 인스턴스를 가져와 보기 좋은 Strings 형식으로 출력합니다. 패키지 수준의 주석은 vet 패키지에 있는 package-info.java 파일에 포함됩니다.

// every java.util.Date class in the vet package should be
// processed by DateAdapter
@XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
package vet;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.Date;

JAXB는 기본적인 자체 방식에 따라 java.util.Date를 XML로 마샬링할 수 있지만 우리의 목적에 더 적합한 다른 사용자 지정 매핑을 사용하도록 JAXB를 수정할 것입니다. 어댑터는 대부분 위와 같은 방법으로 구현합니다.

import java.util.Date;
import java.text.SimpleDateFormat;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends XmlAdapter<String, Date> {

// the desired format
private String pattern = "MM/dd/yyyy";

public String marshal(Date date) throws Exception {
return new SimpleDateFormat(pattern).format(date);
}

public Date unmarshal(String dateString) throws Exception {
return new SimpleDateFormat(pattern).parse(dateString);
}

  개체 모델에 있는 다른 클래스의 주석은 지금까지 본 것과 비슷합니다. 이 팁에 포함된 샘플 코드를 보면 다른 클래스에서 어떤 작업이 이뤄지는지 정확히 알 수 있습니다.

NiceVet의 개체 모델의 주석 처리가 끝났고 XML을 생성할 준비가 되었습니다. 샘플 애플리케이션을 실행하면 다음과 같은 XML 인스턴스 문서가 나타나야 합니다. niceVet.xml 문서는 java-to-schema 프로젝트의 루트 디렉토리에 있습니다.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<printOrder id="1218226109781">
<notifications>
<appointments>
<appointment>
<apptType>Yearly Checkup</apptType>
<apptDate>09/15/2008</apptDate>
<owner>
<firstName>Joe</firstName>
<lastName>Outdoors</lastName>
<address>
<addressLine1>123 Whitewater Street</addressLine1>
<city>OurTown</city>
<state>CA</state>
<zip>90347</zip>
<zipExt>1234</zipExt>
</address>
</owner>
<pet>
<name>Honcho</name>
<species>Dog</species>
</pet>
</appointment>
<appointment>
<apptType>Well Mom Exam</apptType>
<apptDate>09/12/2008</apptDate>
<owner>
<firstName>Missy</firstName>
<lastName>Fairchild</lastName>
<address>
<addressLine1>456 Scenic Drive</addressLine1>
<city>West OurTown</city>
<state>CA</state>
<zip>90349</zip>
<zipExt>6789</zipExt>
</address>
</owner>
<pet>
<name>Miss Kitty</name>
<species>Cat</species>
</pet>
</appointment>
</appointments>
<birthdays>
<birthday>
<age>7</age>
<birthday>09/07/2000</birthday>
<owner>
<firstName>Violet</firstName>
<lastName>Flowers</lastName>
<address>
<addressLine1>22375 Willow Court</addressLine1>
<city>West OurTown</city>
<state>CA</state>
<zip>90349</zip>
<zipExt>6789</zipExt>
</address>
</owner>
<pet>
<name>Tom</name>
<species>Cat</species>
</pet>
</birthday>
</birthdays>
</notifications>
</printOrder>

위에서 XML 인스턴스 문서에 대해 생성한 스키마의 이름은 schema1.xsd이며, java-to-schema 프로젝트 루트 디렉토리 아래의 /generated/schema에 있습니다. 이 스키마의 내용은 다음과 같습니다.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="printOrder" type="PrintOrderType"/>

<xs:complexType name="PrintOrderType">
<xs:sequence>
<xs:element name="notifications" type="notificationsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="id" type="xs:long" use="required"/>
</xs:complexType>

<xs:complexType name="notificationsType">
<xs:sequence>
<xs:element name="appointments" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="appointment" type="AppointmentType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="birthdays" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="birthday" type="BirthdayType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="AppointmentType">
<xs:sequence>
<xs:element name="apptType" type="xs:string"/>
<xs:element name="apptDate" type="xs:string"/>
<xs:element name="owner" type="OwnerType"/>
<xs:element name="pet" type="PetType"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="OwnerType">
<xs:sequence>
<xs:element name="firstName" type="xs:string"/>
<xs:element name="lastName" type="xs:string"/>
<xs:element name="address" type="AddressType"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="AddressType">
<xs:sequence>
<xs:element name="addressLine1" type="xs:string"/>
<xs:element name="addressLine2" type="xs:string" minOccurs="0"/>
<xs:element name="city" type="xs:string"/>
<xs:element name="state" type="xs:string"/>
<xs:element name="zip" type="xs:string"/>
<xs:element name="zipExt" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="PetType">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="species" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="BirthdayType">
<xs:sequence>
<xs:element name="age" type="xs:int"/>
<xs:element name="birthday" type="xs:string"/>
<xs:element name="owner" type="OwnerType"/>
<xs:element name="pet" type="PetType"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

main() 메소드를 잠시 살펴보면 다음과 같은 스키마가 생성되는 것을 알 수 있습니다.

// specify where the generated XML schema will be created
final File dir = new File("generated" + File.separator + "schema");
// specify a name for the generated XML instance document
OutputStream os = new FileOutputStream("niceVet.xml");

// create a JAXBContext for the PrintOrder class
JAXBContext ctx = JAXBContext.newInstance(PrintOrder.class);
// generate an XML schema from the annotated object model; create it
// in the dir specified earlier under the default name, schema1.xsd
ctx.generateSchema(new SchemaOutputResolver() {
@Override
public Result createOutput(String namespaceUri, String schemaName) throws IOException {
return new StreamResult(new File(dir, schemaName));
}
});

XML 인스턴스 문서를 만들려면 먼저 몇 개의 데이터를 만든 다음 JAXBContext에서 마샬러를 가져와 자바 콘텐츠를 XML로 마샬링합니다.

// create some data
PrintOrder order = new PrintOrder(EventUtil.getEvents());

...

// create a marshaller
Marshaller marshaller = ctx.createMarshaller();
// the property JAXB_FORMATTED_OUTPUT specifies whether or not the
// marshalled XML data is formatted with linefeeds and indentation
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// marshal the data in the Java content tree
// to the XML instance document niceVet.xml
marshaller.marshal(order, os);
샘플 애플리케이션 실행
샘플 애플리케이션을 실행하려면 샘플 코드를 다운로드하고 압축을 풉니다. NetBeans를 실행하고 File -> Open Project를 선택합니다. Open Project 대화 상자에서 샘플 코드의 압축을 푼 디렉토리로 이동하여 java-to-schema 폴더를 선택합니다. Open as Main Project 확인란을 선택합니다. Open Project Folder를 클릭합니다. java-to-schema 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 Run Project를 선택합니다.

결론
이 팁에서는 JAXB를 사용하여 XML 데이터를 사업 파트너와 쉽게 교환하는 방법을 살펴봤습니다. 그러나 사업 파트너가 받은 XML 데이터는 해당 시스템에 맞게 조금 조정해야 할 수 있습니다. 앞으로의 팁에서는 제공된 스키마에 사용자 지정을 적용하여 원하는 자바 개체 모델과 콘텐츠 트리를 만드는 방법을 알아보겠습니다.

참고 자료 및 리소스
  • 이 팁의 샘플 코드
  • Java Tutorial
  • Kohsuke Kawaguchi의 블로그 Kohsuke Kawaguchi는 Sun Microsystems의 엔지니어로서, 여러 프로젝트 중 JAXB 관련 프로젝트를 담당합니다.

  • 이 포스트의 작성자 Jennie Hall은 금융 부문에서 근무하는 개발자입니다.

    이 글의 영문 원본은
    Exchanging Data with XML and JAXB, Part 1
    에서 보실 수 있습니다.

    "Java SE" 카테고리의 다른 글

    2008/09/16 18:15 2008/09/16 18:15

    TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/656

    1. XML 및 JAXB를 사용한 데이터 교환, 제 1 부

      Tracked from 하루에 한걸음씩  삭제

      XML 및 JAXB를 사용한 데이터 교환, 제 1 부 Java SE 2008/09/16 18:15 Posted by Sun 이 팁에서는 세부적인 XML 처리 과정을 자세히 알아보지 않고도 자바 SE 6에서 JAXB(Java Architecture for XML Binding)를 사용하여 시스템 간에 XML 데이터를 교환하는 방법에 대해

      2008/12/27 13:57

    댓글을 달아 주세요

    [로그인][오픈아이디란?]

    ◀ Prev 1  ... 37 38 39 40 41 42 43 44 45  ... 641  Next ▶