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#
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을 만들어줘야 한다.
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#
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객체를 만드는 과정이다. 일부 내용을 중략해서 그렇지 전체 소스를 보자면 두배 정도 된다.
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 - 애노테이션 사용#
@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객체, 추가적인 변환자 등이다.
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포맷 변환#
<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 = "&".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 = """.toCharArray(); private static final char[] 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); } } } } |
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() 메소드를 통해 애노테이션 처리가 필요한 클래스를 명시하는 방법이 적절함