태그 : OXM

[번역] XStream 배우기 : 변환기(Converter) 배우기

이글은 XStream 교재 중 "Converter Tutorial"을 번역한 것입니다.

단순한 변환기

준비하기



다음은 가장 기본적인 변환기입니다. 먼저 Person 객체로 시작하겠습니다.

  1. 2분만에 배우기
  2. 별칭(Alias) 배우기
  3. 애노테이션(Annotations) 배우기
  4. 변환기(Converter) 배우기
  5. 객체 스트림(Object Streams) 배우기
  6. 영속화 API(Persistence API) 배우기
  7. JSON 배우기
package com.thoughtworks.xstream.examples;

public class Person {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

이제 인스턴스를 만들고 XML로 변환해보겠습니다.
package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class PersonTest {

public static void main(String[] args) {
Person person = new Person();
person.setName("Guilherme");

XStream xStream = new XStream(new DomDriver());
System.out.println(xStream.toXML(person));
}
}
매우 보기 싫은 결과가 나옵니다. XML 코드에 패키지명을 포함한 전체 클래스 이름이 들어 있습니다.

<com.thoughtworks.xstream.examples.Person>
<name>Guilherme</name>
</com.thoughtworks.xstream.examples.Person>
이제 이 긴 클래스 이름을 보다 인간다운 뭔가(예를 들면 'person')로 바꾸는데 사용할 별칭(alias)를 만듭니다.

XStream xStream = new XStream(new DomDriver());
xStream.alias("person", Person.class);
System.out.println(xStream.toXML(person));
결과물이 한결 일기 쉽고 간결해졌습니다.

<person>
<name>Guilherme</name>
</person>
앞으로 가지고 놀 간단한 클래스를 구성했습니다. XStream 변환기가 어떤 일을 해주는지 보기로 하겠습니다.

PersonConverter 만들기

같이 만들어 볼 간단한 변환기는 이런 기능을 가지고 있습니다.
  1. Person 객체를 변환할 능력이 있는지 말함
  2. Person의 인스턴스를 XML로 변환
  3. XML을 새 Person 인스턴스로 변환
PersonConverter 클래스를 만들고 Converter 인터페이스를 구현하기 시작합니다.

package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class PersonConverter implements Converter {

public boolean canConvert(Class clazz) {
return false;
}

public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
}

public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
return null;
}
}
이제 호출하면 Person 객체만 다룰 수 있다는 것을 얘기해줍니다. Person 외에는 전혀 다룰 수 없습니다. Person을 상속한 클래스를 포함해서......

public boolean canConvert(Class clazz) {
return clazz.equals(Person.class);
}
generic 변환기를 작업하지 않는이상 두번째 단계는 분명합니다.
marshal() 메소드는 객체를 XML로 변환하는 책임을 집니다. 이 메소드는 인수 세개를 받습니다.
  1. 변환하려고 하는 객체
  2. 데이터를 출력할 Writer
  3. 현재의 MarshallingContext
객체를 Person으로 캐스팅을 먼저 합니다.
Person person = (Person) value;
이제 데이터를 출력할 수 있습니다. 'fullname'이라는 노드를 만드는 것으로 시작해서 person 객체의 name 값을 추가합니다.

writer.startNode("fullname");
writer.setValue(person.getName());
writer.endNode();
간단하죠?

public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
Person person = (Person) value;
writer.startNode("fullname");
writer.setValue(person.getName());
writer.endNode();
}
startNode()와 endNode()는 원하는 만큼 여러번 호출할 수 있습니다. 하지만 열어 놓은 것들은 모두 닫아야 한다는 것을 잊지 마십시오.  그리고  변환 작업은 대부분 setValue() 메소드를 호출하는 부분에서 합니다.

unmarshal()로 넘어가보겠습니다. 트리 구조의 자료를 옮겨다니기 위해 moveDown()과 moveUp() 메소드를 사용합니다. 이렇게 단순히 moveDown(), 읽기, moveUp()를 합니다.

                Person person = new Person();
reader.moveDown();
person.setName(reader.getValue());
reader.moveUp();
다음과 같은 변환기가 주어졌습니다.

package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class PersonConverter implements Converter {

public boolean canConvert(Class clazz) {
return clazz.equals(Person.class);
}

public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
Person person = (Person) value;
writer.startNode("fullname");
writer.setValue(person.getName());
writer.endNode();
}

public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Person person = new Person();
reader.moveDown();
person.setName(reader.getValue());
reader.moveUp();
return person;
}
}
이 변환기를 등록하보죠. 우리 프로그램의 main() 메소드는 보는 것 같습니다.

package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class PersonTest {

public static void main(String[] args) {
Person person = new Person();
person.setName("Guilherme");

XStream xStream = new XStream(new DomDriver());
xStream.registerConverter(new PersonConverter());
xStream.alias("person", Person.class);
System.out.println(xStream.toXML(person));
}

}
어떻게 변환기를 등록하는지 보셨나요? 단순히 registerConver()를 호출하면 됩니다.
xStream.registerConverter(new PersonConverter());
최종 결과는 이렇습니다.
<person>
<fullname>Guilherme</fullname>
</person>
아마 이렇게 말할겁니다. "tree만 바뀌었잖아! 나는 데이터를 변환하고 싶어!" 라고......

새 자식 노드를 만드는 대신 person 테그에 fullname이라는 속성을 사용하도록 시도해보세요.

타입을 문자열 표현으로 대체하기

Person 문자열로 표현할 수 있게 향상시켜보겠습니다. 이 문자열은 인스턴스를 다시 만들 때에 필요한 모든 텍스트를 포함하고 있어야 합니다.
package com.thoughtworks.xstream.examples;

public class Person {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String toString() {
return getName();
}
}
이 번 경우에는 변환기를 이렇게 단순화 할 수 있습니다.

package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;

public class PersonConverter extends AbstractSingleValueConverter {

public boolean canConvert(Class clazz) {
return clazz.equals(Person.class);
}

public Object fromString(String str) {
Person person = new Person();
person.setName(string);
return person;
}
}
우리 XML이 더 깔끔해졌을 뿐 아니라 Person 클래스의 별칭(alias) 덕분에 단순히지기까지 했습니다. 문자열 표현이 완성되었으니 이제 내포된 요소(fullname 요소)는 더 이상 필요 없습니다. 
<person>Guilherme</person>
날짜 변환기

이제  Converter 인터페이스가 어떻게 작동하는지 알았으니 정보 변환에 로케일을 사용하는 단순한 달력 변환기를 만들어 보겠습니다.

우리 변환기는 생성자가 Locale을 받아서 멤버 변수에 보관할 것입니다.

 
package com.thoughtworks.xstream.examples;

import java.util.Locale;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class DateConverter implements Converter {

private Locale locale;

public DateConverter(Locale locale) {
super();
this.locale = locale;
}

public boolean canConvert(Class clazz) {
return false;
}

public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
}

public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
return null;
}
}
이제 Calendar를 상속한 모든 객체를 변환 하도록 하겠습니다. 이는 어떤 클래스가 Calendar 클래스에 대응이 될 수 있다면 그 클래스는 추상 클래스 Calendar를 상속했다는 뜻입니다.

public boolean canConvert(Class clazz) {
return Calendar.class.isAssignableFrom(clazz);
}
Calendar를 지역화된 문자열로 변화해보겠습니다.  먼저 객체를 Calendar로 캐스팅 하고 Date를 추출한 다음 지역화된 문자열로 변화하는 날짜 변환기를 얻기 위해 DateFormat 팩토리 메소드를 사용하겠습니다.

public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {

Calendar calendar = (Calendar) value;

// grabs the date
Date date = calendar.getTime();

// grabs the formatter
DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
this.locale);

// formats and sets the value
writer.setValue(formatter.format(date));
}
그리고 이번에는 반대로 복원하기 위해서 GregorianCaldendar를 만들고 지역화된 DateFormat 인스턴스를 얻은 다음 문자열을 파싱해서 Date로 만들고 이 날짜를 원래의 GregorianCaldendar에 넣도록 합니다.
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {

// creates the calendar
GregorianCalendar calendar = new GregorianCalendar();

// grabs the converter
DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
this.locale);

// parses the string and sets the time
try {
calendar.setTime(formatter.parse(reader.getValue()));
} catch (ParseException e) {
throw new ConversionException(e.getMessage(), e);
}

// returns the new object
return calendar;
}
Note 1 : 어떤 DateFormat 구현체는 쓰레드에서 안전하지 않다는 것을 기억하십시오. 따라서,  이 포멧터를 변환기의 멤버 변수로 사용해서는 안됩니다.
Note 2 : 이 구현체는 읽기나 저장 이후에 다른 유형의 카렌다를 GregorianCalendar로 변환합니다. 만약 이 방식을 원하지 않는다면 canConvert() 메소드에서 주어진 class가 GregorianCalendar일 때에만 true를 반환하게 수정하십시오.

이제 변환기를 완성했습니다.

package com.thoughtworks.xstream.examples;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class DateConverter implements Converter {

private Locale locale;

public DateConverter(Locale locale) {
super();
this.locale = locale;
}

public boolean canConvert(Class clazz) {
return Calendar.class.isAssignableFrom(clazz);
}

public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
Calendar calendar = (Calendar) value;
Date date = calendar.getTime();
DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
this.locale);
writer.setValue(formatter.format(date));
  1. }

public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
GregorianCalendar calendar = new GregorianCalendar();
DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
this.locale);
try {
calendar.setTime(formatter.parse(reader.getValue()));
} catch (ParseException e) {
throw new ConversionException(e.getMessage(), e);
}
return calendar;
}
}
확인을 해보겠습니다. main 메소드를 갖는 DateTest를 만듭니다.
  1. 현재시간으로 Calendar를 만든다.
  2. XStream 객체를 만든다.
  3. 변환기를 대한민국 로케일로 등록한다.
  4. 객체를 XML로 변환한다.
자... 이미 이런 작업을 어떻게 해야하는지 알고 있으니 설명 없이 진행하겠습니다.

package com.thoughtworks.xstream.examples;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class DateTest {

public static void main(String[] args) {

// grabs the current date from the virtual machine
Calendar calendar = new GregorianCalendar();

// creates the xstream
XStream xStream = new XStream(new DomDriver());

// brazilian portuguese locale
xStream.registerConverter(new DateConverter(new Locale("ko", "KR")));

// prints the result
System.out.println(xStream.toXML(calendar));

}
}
결과는 로케일에 따라 다르겠지만 이런 형식입니다.
<gregorian-calendar>2006년 2월 10일 금요일</gregorian-calendar>
Note : 위에서 별칭을 따로 주지 않았지만 GregorianCalendar의 기본 별칭인 gregorian-calendar으로 표시되었습니다.

이제 위에 결과를 객체로 복원해보겠습니다.
// loads the calendar from the string
Calendar loaded = (Calendar) xStream
.fromXML("<gregorian-calendar>2006년 2월 10일 금요일</gregorian-calendar>");
그리고 이것을 시스템 로케일의 단축형 날짜 포멧으로 출력해보죠.
// prints using the system defined locale
System.out.println(DateFormat.getDateInstance(DateFormat.SHORT).format(
loaded.getTime()));
결과는 아마도 이렇게 나올 것입니다. (시스템 로케일이 미국 영어라면)
2/10/06
복잡한 변환기

지금까지 만든 객체들을 모아보겠습니다.
package com.thoughtworks.xstream.examples;

public class Birthday {

private Person person;
private Calendar date;

public Person getPerson() {
return person;
}

public void setPerson(Person person) {
this.person = person;
}

public Calendar getDate() {
return date;
}

public void setDate(Calendar date) {
this.date = date;
}
}
XStream은 이 객체를 아무런 문제없이 변환하는 능력을 가지고 있지만 시연을 위해서 직접 변환기를 만들도록 하겠습니다. 이미 만든 Person과 Calendar 변환기를 재사용하는 것이 좋겠습니다. canConvert 메소드는 명백하게 단순합니다. 파생된 객체는 추가 필드를 갖을 수 있기 때문에 여기에서는 변환하지 않겠습니다. 그러나 우리의 멤버 필드와 널 값을 변환하기 위해 XStream이 이미 등록되어 있는 변환기들은 사용하도록 하겠습니다.

package com.thoughtworks.xstream.examples;

import java.util.Calendar;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class BirthdayConverter implements Converter {

public boolean canConvert(Class clazz) {
return Birthday.class == clazz;
}

public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
Birthday birthday = (Birthday)value;
if (value.getPerson() != null) {
writer.startNode("person");
context.convertAnother(value.getPerson());
writer.endNode();
}
if (value.getDate() != null) {
writer.startNode("birth");
context.convertAnother(value.getDate());
writer.endNode();
}
}

public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Birthday birthday = new Birthday();
while (reader.hasMoreChildren()) {
reader.moveDown();
if ("person".equals(reader.getNodeName())) {
Person person = (Person)context.convertAnother(birthday, Person.class);
birthday.setPerson(person);
} else if ("birth".equals(reader.getNodeName())) {
Calendar date = (Calendar)context.convertAnother(birthday, Calendar.class);
birthday.setDate(date);
}
reader.moveUp();
}
return birthday;
}
}
만약 Birthday 구현체의 필드가 확실히 null 값을 갖지 않는다면 변환과 복원 과정에서 null 조건을 제거하고 반복문과 테그 비교문을 생략할 수 있습니다.

package com.thoughtworks.xstream.examples;

import java.util.Calendar;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class BirthdayConverter implements Converter {

public boolean canConvert(Class clazz) {
return Birthday.class == clazz;
}

public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
Birthday birthday = (Birthday)value;
writer.startNode("person");
context.convertAnother(value.getPerson());
writer.endNode();
writer.startNode("birth");
context.convertAnother(value.getDate());
writer.endNode();
}

public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Birthday birthday = new Birthday();
reader.moveDown();
Person person = (Person)context.convertAnother(birthday, Person.class);
birthday.setPerson(person);
reader.moveUp();
reader.moveDown();
Calendar date = (Calendar)context.convertAnother(birthday, Calendar.class);
birthday.setDate(date);
reader.moveUp();
return birthday;
}
}












by 박성철 | 2008/01/25 01:25 | 프로그래밍 이야기 | 트랙백 | 덧글(0)

[번역] XStream 배우기 : 별칭(Alias) 배우기

이글은 XStream 교재 중 "Alias Tutorial"을 번역한 것입니다.

문제

고객이 이미 정의된 기본 XML을 가지고 있어서 XStream으로 이것을 읽고 써야 한다고 가정해봅니다.
<blog author="Guilherme Silveira">
<entry>
<title>first</title>
<description>My first blog entry.</description>
</entry>
<entry>
<title>tutorial</title>
<description>
Today we have developed a nice alias tutorial. Tell your friends! NOW!
</description>
</entry>
</blog>
  1. 2분만에 배우기
  2. 별칭(Alias) 배우기
  3. 애노테이션(Annotations) 배우기
  4. 변환기(Converter) 배우기
  5. 객체 스트림(Object Streams) 배우기
  6. 영속화 API(Persistence API) 배우기
  7. JSON 배우기
위의 XML에 기반해서 모델 클래스 몇개를 만들고 이 포멧을 읽고 쓰도록 XStream을 설정할 것입니다.

모델 객체

XML 파일을 표현할 클래스들은 다음과 같습니다. 가장 먼저 간단한 Blog 객체부터 시작해 보겠습니다. 

First things first, the classes which shall represent our xml files are shown next, beginning with a simple Blog:

package com.thoughtworks.xstream;

public class Blog {
private Author author;
private List entries = new ArrayList();

public Blog(Author author) {
this.author = author;
}

public void add(Entry entry) {
entries.add(entry);
}

public List getContent() {
return entries;
}
}

이름을 갖는 저작자 객체는 이렇습니다.

package com.thoughtworks.xstream;

public class Author {
private String name;
public Author(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

블로그에 등록된 글은 타이틀과 설명을 담고 있습니다.

package com.thoughtworks.xstream;

public class Entry {
private String title, description;
public Entry(String title, String description) {
this.title = title;
this.description = description;
}
}

여기서는 getter와 setter를 만들지 않았지만 코드를 이해하기 좋게 하고 싶다거나 그것들을 있었으면 좋겠다고 생각하면 임의로 만들어도 된다.

간단 테스트

간단히 blog 객체를 생성해서 xstream을 사용해 볼 수 있습니다.
public static void main(String[] args) {

Blog teamBlog = new Blog(new Author("Guilherme Silveira"));
teamBlog.add(new Entry("first","My first blog entry."));
teamBlog.add(new Entry("tutorial",
"Today we have developed a nice alias tutorial. Tell your friends! NOW!"));

XStream xstream = new XStream();
System.out.println(xstream.toXML(teamBlog));

}

하지만 결과는 우리가 원하는 것처럼 깔끔하지 않네요.

<com.thoughtworks.xstream.Blog>
<author>
<name>Guilherme Silveira</name>
</author>
<entries>
<com.thoughtworks.xstream.Entry>
<title>first</title>
<description>My first blog entry.</description>
</com.thoughtworks.xstream.Entry>
<com.thoughtworks.xstream.Entry>
<title>tutorial</title>
<description>
Today we have developed a nice alias tutorial. Tell your friends! NOW!
</description>
</com.thoughtworks.xstream.Entry>
</entries>
</com.thoughtworks.xstream.Blog>


객체 별칭

먼저 할 일은 XStream이 com.thoughtworks.xstream.Blog 클래스를 지칭하는 법을 바꾸는 일입니다. 'blog'를 그 클래스를 가리키는 명칭이라고 하고 blog라는 클래스의 별칭을 만들겠습니다.
xstream.alias("blog", Blog.class);

같은 식으로 Entry 클래스의 별칭도 entry라고 하죠.

xstream.alias("entry", Entry.class);

이제 출력 결과는 이렇게 됩니다.

<blog>
<author>
<name>Guilherme Silveira</name>
</author>
<entries>
<entry>
<title>first</title>
<description>My first blog entry.</description>
</entry>
<entry>
<title>tutorial</title>
<description>
Today we have developed a nice alias tutorial. Tell your friends! NOW!
</description>
</entry>
</entries>
</blog>


entries 테그 빼기

이제 내재적 컬렉션(implicit collection)이라고 부르는 것을 구현해보겠습니다. 루트 테그를 표시할 필요가 없는 컬렉션을 가지고 있다면  내재적 컬렉션으로 대응시킬 수 있습니다.

우리의 경우는 entries 테그를 표시하지 않고 단순히 entry 테그들을 연달아 보여주고 싶습니다.

addImplicitCollection 메소드를 간단히 호출하는 것으로 위에 설명한 것 처럼 우리가 entries 테그를 출력하기 원지 않는 다는 것을XStream이 알게 할 수 있습니다.

package com.thoughtworks.xstream;

import java.util.ArrayList;
import java.util.List;

public class Test {

public static void main(String[] args) {

Blog teamBlog = new Blog(new Author("Guilherme Silveira"));
teamBlog.add(new Entry("first","My first blog entry."));
teamBlog.add(new Entry("tutorial",
"Today we have developed a nice alias tutorial. Tell your friends! NOW!"));

XStream xstream = new XStream();
xstream.alias("blog", Blog.class);
xstream.alias("entry", Entry.class);

xstream.addImplicitCollection(Blog.class, "entries");

System.out.println(xstream.toXML(teamBlog));

}
}

addImplicitCollection 메소드 호출 부분을 주목해서 보십시오. 어떤 클레스와 어떤 멤버 변수가 우리가 원하는 데로 행동할지 알려주고 있습니다.

결과는 우리가 원하는 것과 아주 흡사합니다.

<blog>
<author>
<name>Guilherme Silveira</name>
</author>
<entry>
<title>first</title>
<description>My first blog entry.</description>
</entry>
<entry>
<title>tutorial</title>
<description>
Today we have developed a nice alias tutorial. Tell your friends! NOW!
</description>
</entry>
</blog>


속성 별칭

다음 단계는 author 멤버 변수를 XML 속성이 되게 하는 것 입니다. 이를 위해서는 Blog 클래스의 author 필드를 "author" 속성이 되도록 XStream에게 알려줘야 합니다.
                xstream.useAttributeFor(Blog.class, "author");

이제 한가지 문제만 남았습니다. 어떻게 하면 XStream이 Author 객체를 문자열로 변환해서 xml 테그 속성으로 쓰도록 할 수 있을까요?

SimpeValueConver를 구현해서 Author 변환기를 만들어 보겠습니다.
class AuthorConverter implements SingleValueConverter {
}

먼저 XStream에게 처리 할 타입이 뭔지 알려주는 메소드를 구현하겠습니다.


The first method to implement tells XStream which types it can deal with:

        public boolean canConvert(Class type) {
return type.equals(Author.class);
}

다음은 Author 객체에서 문자열을 추출하는데 사용될 메소드입니다.

        public String toString(Object obj) {
return ((Author) obj).getName();
}

세번째는 반대로 String를 주면 Author 객체를 반환하는 메소드입니다.

        public Object fromString(String name) {
return new Author(name);
}

결국 문자열을 객체(여기서는 Author)로 변환하는 일을 담당 할 단일 값 변환기의 전체 모습은 다음과 같습니다.

class AuthorConverter implements SingleValueConverter {

public String toString(Object obj) {
return ((Author) obj).getName();
}

public Object fromString(String name) {
return new Author(name);
}

public boolean canConvert(Class type) {
return type.equals(Author.class);
}

}

이 변환기를 등록해보겠습니다.

public class Test {

public static void main(String[] args) {

Blog teamBlog = new Blog(new Author("Guilherme Silveira"));
teamBlog.add(new Entry("first","My first blog entry."));
teamBlog.add(new Entry("tutorial",
"Today we have developed a nice alias tutorial. Tell your friends! NOW!"));

XStream xstream = new XStream();
xstream.alias("blog", Blog.class);
xstream.alias("entry", Entry.class);

xstream.addImplicitCollection(Blog.class, "entries");

xstream.useAttributeFor(Blog.class, "author");
xstream.registerConverter(new AuthorConverter());

System.out.println(xstream.toXML(teamBlog));

}
}

결과는?

<blog author="Guilherme Silveira">
<entry>
<title>first</title>
<description>My first blog entry.</description>
</entry>
<entry>
<title>tutorial</title>
<description>
Today we have developed a nice alias tutorial. Tell your friends! NOW!
</description>
</entry>
</blog>

useAttributeFor 메소드와 비슷한 기능을 하는 다른 중첩 메소드가 있는데 이 메소드는 (Class, String, String) 처럼 끝 부분에 필드의 별칭인 문자열을 추가로 받습니다. 예를 들어 useAttributeFor(Blog.clas, "author", "auth")라고 하면 author 필드는 "auth"라는 속성에 대응됩니다.

(역자주 : 이 부분은 이 교제가 잘못된 것  같습니다. XStream에는 패러미터를 세 개 받는 useAttributeFor(Class, String, String) 메소드는 없습니다. 대신 aliasAttribute(Class, String,  String) 메소드가 있습니다.)



종합

별칭(alias)과 변환기(converter)를 사용하면 원하는 거의 대부분의 XML을 구성할 수 있는 충분한 능력 갖게 됩니다.

변환기에 대한 교제를 읽는 것도 잊지 마십시오. XStream을 사용하서 만들 수 있는 다른 유형의 변환기들을 볼 수 있습니다.

by 박성철 | 2008/01/16 13:52 | 프로그래밍 이야기 | 트랙백 | 덧글(0)

[번역] XStream 배우기 : 2분만에 배우는 XStream

XStream은 자바 객체를 XML로 직렬화 시키는 데 사용하는 도구입니다. 다르게 말하면 XML과 객체를 쌍방향 맵핑시켜주는 도구입니다. 자바 객체를 XML로 만들어서 파일이나 DB에 저장하거나 네트워크를 통해서 전송할 수 있게 해줍니다. 거꾸로 이렇게 만들어진 XML을 객체로 복원하기도 하고요. 비슷한 다른 유명 프로젝트로  Castor 와 자바 표준 JAXB 그리고 Quick 등이 있습니다.

XStream 튜토리얼이 한글로 번역된 것이 없어서 시간 될 때마다 하나씩 번역해 올려보겠습니다.
튜토리얼 구성은 이렇습니다.
  1. 2분만에 배우기 (원글)
  2. 별칭(Alias) 배우기 (원글)
  3. 애노테이션(Annotations) 배우기 (원글)
  4. 변환기(Converter) 배우기 (원글)
  5. 객체 스트림(Object Streams) 배우기 (원글)
  6. 영속화 API(Persistence API) 배우기 (원글)
  7. JSON 배우기 (원글)
오늘은 간단한 XStream을 사용법을 전체적으로 볼 수 있는 2분만에 배우기입니다.

2분만에 배우기

이 글은 XStream에 대해 아주 간단히 소개합니다. 대충 읽으시면 객체를 XML로 변환하고 그것을 거꾸로 복원하는 것이 얼마나 단순한지 아실 수 있습니다. 물론 앞으로 궁금한 부분이 생기실 것입니다.

직렬화될 객체 만들기

여기 간단한 클래스 두개가 있습니다. XStream은 이 객체들의 인스턴스를 XML로 변환하고 다시 복원할 수 있습니다.

public class Person {
private String firstname;
private String lastname;
private PhoneNumber phone;
private PhoneNumber fax;
// ... constructors and methods
}

public class PhoneNumber {
private int code;
private String number;
// ... constructors and methods
}

Note: 필드들이 private인 것을 주목하십시오. XStream은 필드의 접근 제한을 무시합니다. getter와 setter 또한 필요하지 않습니다. XStream은 기본 생성자를 꼭 만들어야 하는 제한도 없습니다.


XStream 초기화


XStream을 쓰려면 단순히 XSTream 객체를 생성하십시오.

XStream xstream = new XStream();

클래스 패스에 xstream-[version].jar와 xpp3-[version].jar가 있어야 합니다. XPP3 는 아주 빠른 XML pull parser 구현체입니다. 이 것에 종속되기 원하지 않는다면 대신 표준 JAXP DOM 파서를 사용할 수 있습니다.

XStream xstream = new XStream(new DomDriver()); // XPP3 라이브러리를 요구하지 않음
Note: 이 클래스는 일반적인 동작들을 위해 설계된 간단한 퍼세이드입니다. 보다 큰 융통성을 위해서, 다른 동작을 하는 자신만의 퍼세이드를 만드는 길을 택할 수도 있습니다.

이제 XStream으로 보다 간편히 XML 출력을 만들기위해, 위의 클래스의 이름을 XML 요소명으로 대응하는 별칭을 만들 수 있습니다.

xstream.alias("person", Person.class);
xstream.alias("phonenumber", PhoneNumber.class);

Note: 이 단계는 선택사항입니다. 이 작업 없이도 XStream은 잘 작동하지만 XML 요소 이름이 객체의 패키지를 포함한 전체 이름을 갖게 되기 때문에 조금은 크기가 커집니다. 다음에 진행될 "별칭 배우기" 참고하시면 자세한 내용을 아실 수 있습니다.


객체를 XML로 직렬화하기

Person 객체를 생성하고 필드에 값을 넣도록 하겠습니다.
Person joe = new Person("Joe", "Walnes");
joe.setPhone(new PhoneNumber(123, "1234-456"));
joe.setFax(new PhoneNumber(123, "9999-999"));

이제 XML로 변환하기 위해 해야 하는 것이라고는 단순히 XStream을 호출하는 것뿐 입니다.

String xml = xstream.toXML(joe);

결과는 다음과 같습니다.

<person>
<firstname>Joe</firstname>
<lastname>Walnes</lastname>
<phone>
<code>123</code>
<number>1234-456</number>
</phone>
<fax>
<code>123</code>
<number>9999-999</number>
</fax>
</person>

이렇게 간단합니다. XML이 얼마나 깔끔한지 보십시오.


직렬화된 XML에서 객채를 복원하기


다시 객체를 재조립하려면 이렇게 하십시오.

Person newJoe = (Person)xstream.fromXML(xml);

XStream은 이렇게 단순합니다.


정리

다시 정리해봅니다.

  • xstream.alias(String elementName, Class cls)를 사용해서 자작한 클래스를 요소이름에 대응시킬 클래스 이름의 별칭을 만듭니다. 
  • xstrean.toXML(Object obj)를 사용해서 객체를 XML로 변환합니다.
  • xstream.fromXML(String xml)을 사용해서 XML를 객체로 되돌립니다.

참고 자료

XStream vs Castor for XML serialization  : XStream과 Castor 작업한 경험을 쓴 글입니다.
XML and Java technologies: Data binding, Part 2: Performance : XML과 객체를 바인딩하는 솔루션들의 성능을 비교했습니다. XStream은 빠져있습니다.
Comparing Java Data Binding Tools : Java 객체와 XML의 바인딩 도구들을 비교했습니다. 이 또한 XStream이 나오기 전에 만들어진 문서라서 XStream은 빠져있습니다.
Thoughts on Web Services, part 5: To serialize or not to serialize : XML로 직렬화하는 것에 대한 이슈를 적었습니다.

by 박성철 | 2008/01/15 14:11 | 프로그래밍 이야기 | 트랙백 | 핑백(1) | 덧글(5)

◀ 이전 페이지다음 페이지 ▶