众所周知,xml解析一般有SAX和DOM两种方式。
SAX是基于事件驱动的行解析,具有速度快,占用内存小的优点,但是实现麻烦,代码可读性较差。
DOM是基于树状结构的解析,也就是其将整个xml文档看作一棵树,然后按照不同的分支/节点解析,更具面向对象的风格。缺点就是其需要将整个xml读到内存中,所以占用内存较多,而且比较慢。
实际应用中,很多场景是:程序将xml中的数据解析出来后,然后生成一些java对象来保持这些数据。于是需要在代码中频繁调用创建对象、get/set等的代码。
0.下面是一个使用DOM解析文档的例子。
Address.java
/** * */ package com.hayden.model; /** * @author HaydenWang * */ public class Address implements java.io.Serializable { private static final long serialVersionUID = 1863183127959586043L; private Long id; private String detail; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } @Override public String toString() { return "Address [detail=" + detail + ", id=" + id + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((detail == null) ? 0 : detail.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Address other = (Address) obj; if (detail == null) { if (other.detail != null) return false; } else if (!detail.equals(other.detail)) return false; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } }
User.java
/** * */ package com.hayden.model; import java.util.Date; /** * @author HaydenWang * */ public class User { private Long id; private String name; private Address address; private Date createDate; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((address == null) ? 0 : address.hashCode()); result = prime * result + ((createDate == null) ? 0 : createDate.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (address == null) { if (other.address != null) return false; } else if (!address.equals(other.address)) return false; if (createDate == null) { if (other.createDate != null) return false; } else if (!createDate.equals(other.createDate)) return false; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { return "User [address=" + address + ", createDate=" + createDate + ", id=" + id + ", name=" + name + "]"; } }
Domparser.java
/** * */ package com.hayden.dom; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.hayden.castor.XmlUtil; import com.hayden.model.Address; import com.hayden.model.User; /** * @author HaydenWang * */ public class DomParser { private static final Logger logger = Logger.getLogger(DomParser.class); private static final String FORMAT = "yyyy-MM-dd"; public User parseUser(String fileName) { Document doc = XmlUtil.getDocumentFromFile(fileName); Node node = doc.getFirstChild(); User user = new User(); if ("user".equals(node.getNodeName())) { NodeList childNodes = node.getChildNodes(); int length = childNodes.getLength(); for (int i = 0; i < length; i++) { Node subNode = childNodes.item(i); String nodeName = subNode.getNodeName(); if ("address".equals(nodeName)) {// this is address node Address addr = parseAddress(subNode); user.setAddress(addr); } else if ("name".equals(nodeName)) { // this is name node String userName = getNodeValue(subNode); user.setName(userName); } else if ("id".equals(nodeName)) { String idString = getNodeValue(subNode); user.setId(Long.parseLong(idString)); }else if("create-date".equals(nodeName)){ String dateString = getNodeValue(subNode); Date date = parseDate(dateString); user.setCreateDate(date); } } } return user; } private Address parseAddress(Node addrNode) { Address addr = new Address(); NodeList nodeList = addrNode.getChildNodes(); int length = nodeList.getLength(); for (int i = 0; i < length; i++) { Node node = nodeList.item(i); String nodeName = node.getNodeName(); if ("id".equals(nodeName)) { String idString = getNodeValue(node); addr.setId(Long.parseLong(idString)); } else if ("details".equals(nodeName)) { addr.setDetail(getNodeValue(node)); } } return addr; } /** * get the value for the node with format <name>helen</name> * * @param node * @return */ private String getNodeValue(Node node) { String nodeValue = null; if (null != node) { NodeList childNodeList = node.getChildNodes(); if (childNodeList != null) { for (int i = 0, j = childNodeList.getLength(); i < j; i++) { Node currentNode = childNodeList.item(i); nodeValue = currentNode.getNodeValue(); break; } } else { logger.debug("The node is empty"); } logger.debug(" The nodeName: " + node.getNodeName() + ", and the value: " + nodeValue); } else { logger.warn("The node is null, so can't take its value"); } return nodeValue; } private static final Date parseDate(String input){ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT); Date date = null; try { date = formatter.parse((String) input); } catch (ParseException px) { throw new IllegalArgumentException(px.getMessage()); } return date; } }
通过上面的代码可以看出,有很多重复性的体力活(频繁判断节点的内容,然后set到对象中),而且当我们需要给这些对象增加或者修改一些属性的时候,就需要修改解析xml的程序。
那么是否有工具可以帮助实现这些事情呢?这就是Castor等Xml-Object mapping的工具做的事情。
Castor中有两个核心的操作:
下面分别看看Castor在几种不同场景下的使用。
1. 默认情况下,Castor会自动扫描类中的set/get方法,会自动将该属性和xml中的节点对应起来。如果属性不是String类型,在marshaller的时候将调用其toString()方法,同样在unmarshaller的时候,也会自动将其转型到所需类型。下面是一个使用Castor默认配置的例子。
/** * */ package com.hayden.castor; import static org.junit.Assert.fail; import java.io.FileReader; import java.io.FileWriter; import java.util.Date; import org.exolab.castor.xml.Marshaller; import org.exolab.castor.xml.Unmarshaller; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.hayden.model.Address; import com.hayden.model.User; /** * @author HaydenWang * */ public class SimpletestCastor { private User user; @Before public void init() { Address addr = new Address(); addr.setId(100l); addr.setDetail("Baidu Building"); user = new User(); user.setId(1111L); user.setName("Helen"); user.setAddress(addr); user.setCreateDate(new Date()); } @Test public void simpleTest() { try { FileWriter writer = new FileWriter("simpleTest.xml"); Marshaller.marshal(user, writer); // Create a Reader to the file to unmarshal from FileReader reader = new FileReader("simpleTest.xml"); // Marshal the user object User userFromXml = (User) Unmarshaller.unmarshal(User.class, reader); System.out.println(userFromXml); Assert.assertEquals(user, userFromXml); } catch (Exception e) { e.printStackTrace(); fail(e.getMessage()); } } }
2. 当默认的mapping不能满足要求时,比如有的时候我们可能需要去节点的某个属性的值map到对象的属性,有的时候则是将某个节点的值map到对象的属性,这个时候我们就需要自己定义mapping文件。
userMapping.xml
<?xml version="1.0"?> <!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN" "http://castor.org/mapping.dtd"> <mapping> <class name="com.hayden.model.Address"> <map-to xml="address" /> <field name="id" type="java.lang.Long"> <bind-xml name="id" node="element" /> </field> <field name="detail" type="java.lang.String"> <bind-xml name="detail" node="element" /> </field> </class> <class name="com.hayden.model.User"> <map-to xml="user" /> <field name="id" type="java.lang.Long"> <bind-xml name="id" node="element" /> </field> <field name="name" type="java.lang.String"> <bind-xml name="name" node="element" /> </field> <field name="address" type="com.hayden.model.Address"> <bind-xml name="address" node="element" /> </field> <field name="type" type="java.lang.Integer"> <bind-xml name="type" node="element" /> </field> </class> </mapping>
CastorMappingTest.java
/** * */ package com.hayden.castor; import static org.junit.Assert.fail; import java.io.FileReader; import java.io.FileWriter; import org.exolab.castor.mapping.Mapping; import org.exolab.castor.xml.Marshaller; import org.exolab.castor.xml.Unmarshaller; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.hayden.model.Address; import com.hayden.model.User; /** * @author HaydenWang * */ public class CastorMappingTest { private User user; @Before public void init() { Address addr = new Address(); addr.setId(100l); addr.setDetail("Baidu Building"); user = new User(); user.setId(1111L); user.setName("Helen"); user.setAddress(addr); user.setType(User.TYPE_COMMON_USER); } @Test public void mappingTest() { try { // Load Mapping Mapping mapping = new Mapping(); mapping.loadMapping("file:conf//userMapping.xml"); Marshaller marshaller = new Marshaller(); marshaller.setMapping(mapping); FileWriter writer = new FileWriter("out//userMappingTest.xml"); marshaller.setWriter(writer); marshaller.marshal(user); // Create a Reader to the file to unmarshal from FileReader reader = new FileReader("out//userMappingTest.xml"); // Create a new Unmarshaller Unmarshaller unmarshaller = new Unmarshaller(mapping); unmarshaller.setClass(User.class); // Unmarshal the user object User userFromXml = (User) unmarshaller.unmarshal(reader); System.out.println(userFromXml); Assert.assertEquals(user, userFromXml); } catch (Exception e) { e.printStackTrace(); fail(e.getMessage()); } } }
3. 以上的例子都是直接将xml中的值赋给对象的属性,可是有的时候,我们需要将这些值进行包装。如上面例子中的用户的type属性,在代码中是Long类型,可是我们希望在xml中是有意义的字符,如对于type为1的时候,输出“admin”。对于这种情况,这个时候我们就需要写一些FieldHandler来解决这类问题。
UserTypeHandler.java
package com.hayden.castor; import org.exolab.castor.mapping.GeneralizedFieldHandler; import com.hayden.model.User; public class UserTypeHandler extends GeneralizedFieldHandler { @Override public Object convertUponGet(Object value) { Long type = (Long) value; if (User.TYPE_ADMIN.equals(type)) { return "admin"; } else { return "common user"; } } @Override public Object convertUponSet(Object value) { String typeString = (String) value; if ("admin".equals(typeString)) { return User.TYPE_ADMIN; } else { return User.TYPE_COMMON_USER; } } @Override public Class getFieldType() { return Long.class; } }
fieldHandlerMapping.xml
<?xml version="1.0"?> <!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN" "http://castor.org/mapping.dtd"> <mapping> <class name="com.hayden.model.Address"> <map-to xml="address" /> <field name="id" type="java.lang.Long"> <bind-xml name="id" node="element" /> </field> <field name="detail" type="java.lang.String"> <bind-xml name="detail" node="element" /> </field> </class> <class name="com.hayden.model.User"> <map-to xml="user" /> <field name="id" type="java.lang.Long"> <bind-xml name="id" node="element" /> </field> <field name="name" type="java.lang.String"> <bind-xml name="name" node="element" /> </field> <field name="address" type="com.hayden.model.Address"> <bind-xml name="address" node="element" /> </field> <field name="type" type="java.lang.String" handler="com.hayden.castor.UserTypeHandler"> <bind-xml name="type" node="element" /> </field> </class> </mapping>
FieldHandlerMappingTest.java
/** * */ package com.hayden.castor; import static org.junit.Assert.fail; import java.io.FileReader; import java.io.FileWriter; import org.exolab.castor.mapping.Mapping; import org.exolab.castor.xml.Marshaller; import org.exolab.castor.xml.Unmarshaller; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.hayden.model.Address; import com.hayden.model.User; /** * @author HaydenWang * */ public class FieldHandlerMappingTest { private User user; @Before public void init() { Address addr = new Address(); addr.setId(100l); addr.setDetail("Baidu Building"); user = new User(); user.setId(1111L); user.setName("Helen"); user.setAddress(addr); user.setType(User.TYPE_COMMON_USER); } @Test public void mappingTest() { try { // Load Mapping Mapping mapping = new Mapping(); mapping.loadMapping("file:conf//fieldHandlerMapping.xml"); Marshaller marshaller = new Marshaller(); marshaller.setMapping(mapping); FileWriter writer = new FileWriter("out//fieldHandlerTest.xml"); marshaller.setWriter(writer); marshaller.marshal(user); // Create a Reader to the file to unmarshal from FileReader reader = new FileReader("out//fieldHandlerTest.xml"); // Create a new Unmarshaller Unmarshaller unmarshaller = new Unmarshaller(mapping); unmarshaller.setClass(User.class); // Unmarshal the user object User userFromXml = (User) unmarshaller.unmarshal(reader); System.out.println(userFromXml); Assert.assertEquals(user, userFromXml); } catch (Exception e) { e.printStackTrace(); fail(e.getMessage()); } } }
以上只是Castor的基本使用方法。
更多请参见:
http://www.castor.org/
此外,sun的xerecs具有类似的功能。