티스토리 뷰

카테고리 없음

XStream (Object To XML)

민깡 2013. 5. 24. 09:29

XStream #

  • 홈페이지 : http://xstream.codehaus.org/index.html
  • XStream이란 : 객체를 XML로 변환하거나 XML을 객체로 변환하기 위한 간단한 라이브러리
  • 특징
    • Ease of use
    • No mappings required
    • Performance.
    • Clean XML.
    • Requires no modifications to objects.
    • Full object graph support.
    • Integrates with other XML APIs.
    • Customizable conversion strategies.
    • Error messages.
    • Alternative output format.

Object To XML#

  • XStream 을 사용하지 않는 경우

public String personToXML(Person person) {
StringBuffer xml = new StringBuffer("");

xml.append("<person>\n");
xml.append(" <firstname>" + person.getFirstname() + "</firstname>\n");
xml.append(" <lastname><![CDATA[" + person.getLastname() + "]]></lastname>\n");
xml.append(" <phone>\n");
xml.append(" <code>" + person.getPhone().getCode() + "</code>\n");
xml.append(" <number><![CDATA[" + person.getPhone().getNumber() + "]]></number>\n");
xml.append(" </phone>\n");
List<Friend> friendList = person.getFriendList();
for (Friend friend : friendList) {
xml.append(" <friend>\n");
xml.append(" <name><![CDATA[" + friend.getName() + "]]></name>\n");
xml.append(" <age>" + friend.getAge() + "</age>\n");
xml.append(" </friend>\n");
}
xml.append("</person>");

return xml.toString();
}

개발방법마다 다르겠지만 xstream과 같은 매퍼를 사용하지 않으면 위처럼 클래스내 변수들을 로직으로 처리해서 xml을 만들어줘야 한다.

  • XStream 을 사용하는 경우

public String personToXML(Person person) {
String xml = "";
xstream = new XStream(new LocalXppDriver());

xstream.alias("person", Person.class);
xstream.alias("friend", Friend.class);
xstream.addImplicitCollection(Person.class, "friendList", Friend.class);
xml = xstream.toXML(person);

return xml;
}

alias와 암시적인 collection객체 정보만 설정하면 같은 xml이 반환된다.

XML To Object#

  • XStream 을 사용하지 않는 경우

public Person xmlToPerson(String xml) {
Person person = new Person();

StringReader reader = new StringReader(xml);
InputSource source = new InputSource(reader);
Document document = null;
try {
document = builder.parse(source);
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

PhoneNumber phoneNumber = null;
Friend friend = null;
List<Friend> friendList = new ArrayList<Friend>();

Node rootNode = document.getFirstChild();
NodeList nodeList = rootNode.getChildNodes();
NodeList subNodeList = null;
Node node = null;
Node subNode = null;

int nodeListLength = nodeList.getLength();
int subNodeListLength = 0;
String nodeName = "";
for (int i = 0; i < nodeListLength; i++) {
node = nodeList.item(i);
nodeName = node.getNodeName();
if (nodeName.equals("firstname")) {
person.setFirstname(node.getChildNodes().item(0).getNodeValue());
} else if (nodeName.equals("lastname")) {
person.setLastname(node.getChildNodes().item(0).getNodeValue());
} else if (nodeName.equals("phone")) {
subNodeList = node.getChildNodes();
subNodeListLength = subNodeList.getLength();

phoneNumber = new PhoneNumber();
for (int j = 0; j < subNodeListLength; j++) {
subNode = subNodeList.item(j);
if( j==1 ){
phoneNumber.setCode(Integer.parseInt(subNode.getChildNodes().item(0).getNodeValue()));
}else if( j==3 ){
phoneNumber.setNumber(subNode.getChildNodes().item(0).getNodeValue());
}
}
person.setPhone(phoneNumber);
} else if (nodeName.equals("friend")) {
subNodeList = node.getChildNodes();
subNodeListLength = subNodeList.getLength();

friend = new Friend();
for (int j = 0; j < subNodeListLength; j++) {
subNode = subNodeList.item(j);
if( j==1 ){
friend.setName(subNode.getChildNodes().item(0).getNodeValue());
}else if( j==3 ){
friend.setAge(Integer.parseInt(subNode.getChildNodes().item(0).getNodeValue()));
}
}
friendList.add(friend);
}
person.setFriendList(friendList);
}

return person;
}

위 소스는 DOM을 사용하여 xml를 파싱한 후 Person객체를 만드는 과정이다. 일부 내용을 중략해서 그렇지 전체 소스를 보자면 두배 정도 된다.

  • XStream 을 사용하는 경우

public Person xmlToPerson(String xml) {
Person person = null;
xstream = new XStream(new LocalXppDriver());

xstream.alias("person", Person.class);
xstream.alias("friend", Friend.class);
xstream.addImplicitCollection(Person.class, "friendList", Friend.class);
person = (Person) xstream.fromXML(xml);

return person;
}

이 역시 alias와 암시적인 collection객체 설정만 해주면 된다.

XML To Object - 애노테이션 사용#

  • Model 객체에 애노테이션 사용

@XStreamAlias("person")
public class Person {
@XStreamOmitField
private String firstname = null;
private String lastname = null;
private PhoneNumber phone = null;
@XStreamImplicit(itemFieldName = "friend")
private List<Friend> friendList = null;

애노테이션을 사용하여 처리할수 있는 부분은 별칭, xml표현에서 생략하기, 암시적인 collection객체, 추가적인 변환자 등이다.

  • XStream을 사용하는 소스

public String personToXML(Person person) {
String xml = "";
xstream = new XStream(new LocalXppDriver());

Class[] types = { Person.class, Friend.class, PhoneNumber.class };
xstream.processAnnotations(types);
xml = xstream.toXML(person);

return xml;
}

애노테이션으로 alias및 collection객체에 관련된 설정을 했기 때문에 애노테이션 처리를 해야 하는 클래스를 명시적으로 선언하는 부분만 추가되고 끝난다.

애노테이션#

  • 사용가능한 애노테이션
    • XStreamAlias : 클래스및 변수명에 대한 별칭, 클래스의 경우 기본은 패키지 경로를 포함한 클래스명을 표기하기 때문에 일반적으로 단축형의 별칭 사용이 필요
    • XStreamAsAttribute : 클래스및 변수를 xml요소가 아닌 xml속성으로 사용
    • XStreamConverter : 클래스및 변수를 xml요소로 바꿀때 추가적인 로직을 적용할수 있는 변환자 명시
    • XStreamConverters : 변환자를 복수로 명시
    • XStreamImplicit : collection객체를 암시적으로 명시, xml 표현에서는 제외되도록 처리
    • XStreamOmitField : xml표현에서 뺌
  • deprecated된 애노테이션
    • XStreamContainedType
    • XStreamImplicitCollection

변환자(Converter)#

  • Converter 인터페이스를 구현해야 함
  • 샘플

public class PhoneConverter implements Converter {

public void marshal(Object arg0, HierarchicalStreamWriter arg1, MarshallingContext arg2) {
PhoneNumber phoneNumber = (PhoneNumber) arg0;
arg1.startNode("code");
arg1.setValue(phoneNumber.getCode() + "");
arg1.endNode();
arg1.startNode("number");
arg1.setValue(phoneNumber.getNumber() + "");
arg1.endNode();
}

public Object unmarshal(HierarchicalStreamReader arg0, UnmarshallingContext arg1) {
PhoneNumber phoneNumber = new PhoneNumber();
if( arg0.hasMoreChildren() ){
arg0.moveDown();
if ("code".equals(arg0.getNodeName().trim())) {
phoneNumber.setCode(Integer.parseInt(arg0.getValue()));
} else if ("number".equals(arg0.getNodeName().trim())) {
phoneNumber.setNumber("converted : "+arg0.getValue());
}
arg0.moveUp();
}
return phoneNumber;
}

@SuppressWarnings("unchecked")
public boolean canConvert(Class arg0) {
return true;
}

}

  • 변환자 등록
    • registerConverter() 메소드 사용(전체적용)
    • registerLocalConverter() 메소드 사용(일부 클래스및 변수에만 적용)
    • @XStreamConverter(value = PhoneConverter.class) 애노테이션 사용

XSLT를 이용한 XML포맷 변환#

  • 샘플 XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="/cat">
<xsl:copy>
<xsl:apply-templates select="mName"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

  • 샘플 소스

XStream xstream = new XStream();
xstream.alias("cat", Cat.class);

TraxSource traxSource = new TraxSource(new Cat(4, "Garfield"), xstream);
Writer buffer = new StringWriter();
Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(new StringReader(XSLT)));
transformer.transform(traxSource, new StreamResult(buffer));

  • 결과물

<cat>
<mName>Garfield</mName>
</cat>

XStream 확장하기#

  • 확장 케이스
    • CDATA 처리를 위한 기능이 없음.
    • PrettyPrintWriter 클래스를 확장하는 방법으로 구현.
    • writeText() 메소드 부분 구현
  • 소스
    • LocalPrettyPrintWriter.java

package openframework.study.mapper.xstream.ext;

import java.io.Writer;
import java.util.regex.Pattern;

import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;

/**
* XStream(http://xstream.codehaus.org/)을 사용하는 확장 PrettyPrintWriter클래스
*
* @since 2008.05.30
*/
public class LocalPrettyPrintWriter extends PrettyPrintWriter {
private static final char[] AMP = "&amp;".toCharArray();
private static final char[] LT = "<".toCharArray();
private static final char[] GT = ">".toCharArray();
private static final char[] SLASH_R = " ".toCharArray();
private static final char[] QUOT = "&quot;".toCharArray();
private static final char[] APOS = "&apos;".toCharArray();

public LocalPrettyPrintWriter(Writer writer) {
super(writer);
}

/**
* 실제 text값을 xml태그를 통해 표현하는 메소드이다.
* 상위 클래스인 PrettyPrintWriter의 다른 기능을 그대로 사용하되 CDATA처리를 위해 오버라이딩했다. .
*/
protected void writeText(QuickWriter writer, String text) {
/**
* 값이 없을 경우 <태그 /> 형태로 그냥 반환해버린다.
* 차후 값이 없더라도 CDATA로 싸서 공백을 반환해야 하는 경우 아래 조건을 없애면 된다.
*/
if( text.trim().length() < 1 ){
super.writeText(writer, text);
return;
}

String CDATAPrefix = "<![CDATA[";
String CDATASuffix = "]]>";

/**
* 실제 CDATA를 추가해주는 부분
* CDATA에 대한 추가 로직이 필요하거나 CDATA사용이 필요없을 경우에는 아래 부분을 제거하지 말고 상위 클래스 사용을 권장한다.
*/
if (!text.startsWith(CDATAPrefix) && !Pattern.matches("[^[0-9]]+", text)) {
text = CDATAPrefix+text+CDATASuffix;
}

int length = text.length();
if (!text.startsWith(CDATAPrefix)) {
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
switch (c) {
case '&':
writer.write(AMP);
break;
case '<':
writer.write(LT);
break;
case '>':
writer.write(GT);
break;
case '"':
writer.write(QUOT);
break;
case '\'':
writer.write(APOS);
break;
case '\r':
writer.write(SLASH_R);
break;
default:
writer.write(c);
}
}
} else {
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
writer.write(c);
}
}
}
}

    • LocalXppDriver.java

package openframework.study.mapper.xstream.ext;

import java.io.Writer;

import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;

/**
* XStream(http://xstream.codehaus.org/)을 사용하는 확장 XppDriver클래스
*
* @since 2008.05.30
*/
public class LocalXppDriver extends XppDriver {

public HierarchicalStreamWriter createWriter(Writer out) {
return new LocalPrettyPrintWriter(out);
}
}

XStream사용시 주의할 점#

  • autodetectAnnotations() 메소드 사용
    • 동시성 문제를 발생시킬 가능성
    • model 객체내 모든 애노테이션을 감지하는 것은 불가능
    • processAnnotations() 메소드를 통해 애노테이션 처리가 필요한 클래스를 명시하는 방법이 적절함
공지사항
최근에 올라온 글