天天看點

Java對象與xml轉換

在項目中經常會遇到xml與Object的轉換,即java對象序列号為xml文檔,xml文檔可以反序列化為java對象。目前比較好用的有jaxb和XStream。下面進行分别介紹

一、JAXB

    Java  Architecture for XML Binding (JAXB) 是一個業界的标準,是一項可以根據XML Schema産生Java類的技術。

廢話不多說,直接上例子,假設我們需要實作一個簡單的學校學生系統,我們的java對象如下:

/**
 * @author ozl
 * 基類
 */
public abstract class AbstracElement {

	private String address;
	
	private String name;

	/**
	 * @return the address
	 */
	public String getAddress() {
		return address;
	}

	/**
	 * @param address the address to set
	 */
	public void setAddress(String address) {
		this.address = address;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}
}
//學生對象
public class Student extends AbstracElement {

	// 出生日期
	private XMLGregorianCalendar birthDay;

	/**
	 * @return the birthDay
	 */
	public XMLGregorianCalendar getBirthDay() {
		return birthDay;
	}

	/**
	 * @param birthDay
	 *            the birthDay to set
	 */
	public void setBirthDay(XMLGregorianCalendar birthDay) {
		this.birthDay = birthDay;
	}
}

public class School extends AbstracElement {

	private List<Student> students;

	private int studentCount;
	
	/**
	 * @return the students
	 */
	public List<Student> getStudents() {
		return students;
	}
	/**
	 * @return the studentCount
	 */
	public int getStudentCount() {
		 studentCount=getStudents().size();
		 return this.studentCount;
	}
}
           

 jaxb通過注解的關聯java對象和序列号的方式,現在我們着重介紹一下常用的xml.binding提供的注解。

@XmlAccessorType 控制預設序列号使用javabean的屬性或者字段,值為XmlAccessType類型,當為PROPERTY時表示每一個setter/getter方法對都是序列号、FIELD表示字段、PUBLIC_MEMBER表示FIELD或者getter/setter方法。

@XmlRootElement:映射xml的根節點,name和namespace方法

@XmlElement:表示一個子節點,裡面可以有name和namespace方法

@XmlAttribute:表示屬性。

現在我們在上面的java代碼上就可以使用上面的注解實作序列化了。

我們在基類AbstracElement中把name作為節點的屬性,在address作為字元素處理,代碼如下:

/**
	 * @return the address
	 */
	@XmlElement(name="address",namespace=JaxbTest.default_namespace)
	public String getAddress() {
		return address;
	}

	/**
	 * @return the name
	 */
	@XmlAttribute
	public String getName() {
		return name;
	}
修改School類如下:
@XmlAccessorType(XmlAccessType.FIELD)  
@XmlRootElement(name="school",namespace=JaxbTest.default_namespace)     //定義根節點的名字和命名空間
public class School extends AbstracElement {

	@XmlElement(name="student",namespace=JaxbTest.default_namespace )
	private List<Student> students;
//其他的方法省略了
}

@XmlAccessorType(XmlAccessType.PROPERTY)
public class Student extends AbstracElement {

	// 出生日期
	// @XmlElement //如果此時再加上這個注解就會報錯,發現兩個birthDay屬性
	private XMLGregorianCalendar birthDay;

	/**
	 * @return the birthDay
	 */
	@XmlElement(namespace = JaxbTest.default_namespace)
	public XMLGregorianCalendar getBirthDay() {
		return birthDay;
	}
//省略了其他的方法
}

我們的測試代碼如下:
public class JaxbTest {

	public static final String default_namespace = "http://www.ozl.com/test.default";
	public static final String extend_namespace = "http://www.ozl.com/test.extend";

	/**
	 * @param args
	 * @throws JAXBException
	 * @throws DatatypeConfigurationException
	 */
	public static void main(String[] args) throws JAXBException,
			DatatypeConfigurationException {
		marshallerXML();
		unmarshaller();
	}

	public static void marshallerXML() throws JAXBException,
			DatatypeConfigurationException {
		JAXBContext jc = JAXBContext.newInstance(School.class);
		School school = new School();
		school.setName("實驗");
		school.setAddress("南京雨花台區");
		List<Student> students = new ArrayList<>();
		school.setStudents(students);

		Student student1 = new Student();
		student1.setName("張三");
		student1.setAddress("南京市xx路");
		XMLGregorianCalendar c = DatatypeFactory.newInstance()
				.newXMLGregorianCalendarDate(2014, 11, 11,
						DatatypeConstants.FIELD_UNDEFINED);
		student1.setBirthDay(c);
		students.add(student1);

		Student student2 = new Student();
		student2.setName("李四");
		student2.setAddress("濟南市xx路");
		students.add(student2);

		Student student3 = new Student();
		student3.setName("王五");
		student3.setAddress("濟南市xx路");
		students.add(student3);

		Marshaller marshaller = jc.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		marshaller.setProperty(Marshaller.JAXB_ENCODING, "GBK");
		marshaller.marshal(school, System.out);
	}

	public static void unmarshaller() throws JAXBException {
		InputStream io = JaxbTest.class.getResourceAsStream("school.xml");
		JAXBContext jc = JAXBContext.newInstance(School.class);
		Unmarshaller unmarshaller = jc.createUnmarshaller();
		School school = (School) unmarshaller.unmarshal(io);
		System.out.println(school.getStudentCount());
	}
}

輸出結果為:
<?xml version="1.0" encoding="GBK" standalone="yes"?>
<school name="實驗" xmlns="http://www.ozl.com/test.default">
    <address>南京雨花台區</address>
        <student name="張三">
            <address>南京市xx路</address>
            <birthDay>2014-11-11</birthDay>
        </student>
        <student name="李四">
            <address>濟南市xx路</address>
        </student>
        <student name="王五">
            <address>濟南市xx路</address>
        </student>
</school>
           

 現在我需要在student的子節點上包裝一層節點,需要使用@XmlElementWrapper注解,Student類的students字段上增加如下注解:

@XmlElementWrapper(name="students",namespace=JaxbTest.default_namespace )    //多包一層節點
	@XmlElement(name="student",namespace=JaxbTest.default_namespace )
	private List<Student> students;
           

 如果此時再運作我們的測試代碼,輸出如下:

<?xml version="1.0" encoding="GBK" standalone="yes"?>

<school name="實驗" xmlns="http://www.ozl.com/test.default">

    <address>南京雨花台區</address>

    <students>

        <student name="張三">

               <address>南京市xx路</address>

               <birthDay>2014-11-11</birthDay>

         </student>

         <student name="李四">

                  <address>濟南市xx路</address>

         </student>

         <student name="王五">

                   <address>濟南市xx路</address>

          </student>

         <students>

</school>

現在我需要記錄學生的興趣愛好,在Student類上增加List<String> hobbies字段。我們修改測試代碼和Student類如下:

// 星期愛好
	private List<String> hobbies;
	@XmlElement(name="hobby" ,namespace=JaxbTest.default_namespace)    //自定義節點的名字
	public List<String> getHobbies() {
		return hobbies;
	}

修改測試代碼的marshallerXML()方法如下:

		JAXBContext jc = JAXBContext.newInstance(School.class);
		School school = new School();
		school.setName("實驗");
		school.setAddress("南京雨花台區");

		List<Student> students = new ArrayList<>();
		school.setStudents(students);

		Student student1 = new Student();
		student1.setName("張三");
		student1.setAddress("南京市xx路");
		List<String> hobbies = new ArrayList<>();
		hobbies.add("遊泳");
		hobbies.add("乒乓球");
		student1.setHobbies(hobbies);
		XMLGregorianCalendar c = DatatypeFactory.newInstance()
				.newXMLGregorianCalendarDate(2014, 11, 11,
						DatatypeConstants.FIELD_UNDEFINED);
		student1.setBirthDay(c);
		students.add(student1);

		Student student2 = new Student();
		student2.setName("李四");
		student2.setAddress("濟南市xx路");
		hobbies = new ArrayList<>(hobbies);
		hobbies.add(0, "籃球");
		student2.setHobbies(hobbies);
		students.add(student2);

		Student student3 = new Student();
		student3.setName("王五");
		student3.setAddress("濟南市xx路");
		hobbies = new ArrayList<>(hobbies);
		hobbies.remove(1);
		student3.setHobbies(hobbies);
		students.add(student3);

		Marshaller marshaller = jc.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		marshaller.setProperty(Marshaller.JAXB_ENCODING, "GBK");
		marshaller.marshal(school, System.out);
	

測試結果如下:
<?xml version="1.0" encoding="GBK" standalone="yes"?>
<school name="實驗" xmlns="http://www.ozl.com/test.default">
    <address>南京雨花台區</address>
    <students>
        <student name="張三">
            <address>南京市xx路</address>
            <birthDay>2014-11-11</birthDay>
            <hobby>遊泳</hobby>
            <hobby>乒乓球</hobby>
        </student>
        <student name="李四">
            <address>濟南市xx路</address>
            <hobby>籃球</hobby>
            <hobby>遊泳</hobby>
            <hobby>乒乓球</hobby>
        </student>
        <student name="王五">
            <address>濟南市xx路</address>
            <hobby>籃球</hobby>
            <hobby>乒乓球</hobby>
        </student>
    </students>
</school>
           

  發現hobby列了很多子節點,但是我需要序列号為一個節點,這時候可以使用@XmlJavaTypeAdapter來實作自己序列号或者反序列化的規則,現在修改如下:

/**
	 * @return the hobbies
	 */
	@XmlJavaTypeAdapter(ListXmlAdapter.class)       //自定義序列号和反序列化方式
	@XmlElement(name="hobby" ,namespace=JaxbTest.default_namespace)    //自定義節點的名字
	public List<String> getHobbies() {
		return hobbies;
	}


而ListXmlAdapter類如下:
/**
 * @author ozl
 *
 */
public class ListXmlAdapter extends XmlAdapter<String, List<String>> {

	@Override
	public List<String> unmarshal(String v) throws Exception {
	String[] values = v.split("#");
		return new ArrayList<String>(Arrays.asList(values));
	}

	@Override
	public String marshal(List<String> v) throws Exception {
		StringBuffer buffer=new StringBuffer();
		for(int i=0;i<v.size()-1;i++)
		{
			buffer.append(v.get(i));
			buffer.append("#");
		}
		if(v.size()>0)
		{
			buffer.append(v.get(v.size()-1));
		}
		return buffer.toString();
	}

}
           

 此時再運作我們的測試代碼,得到的結果如下:

<?xml version="1.0" encoding="GBK" standalone="yes"?>

      <school name="實驗" xmlns="http://www.ozl.com/test.default">

            <address>南京雨花台區</address>

            <students>

                 <student name="張三">

                       <address>南京市xx路</address>

                       <birthDay>2014-11-11</birthDay>

                       <hobby>遊泳#乒乓球</hobby>

                 </student>

                  <student name="李四">

                         <address>濟南市xx路</address>

                        <hobby>籃球#遊泳#乒乓球</hobby>

                   </student>

                  <student name="王五">

                        <address>濟南市xx路</address>

                        <hobby>籃球#乒乓球</hobby>

                  </student>

              </students>

 </school>  

控制序列号的順序:有的時候我們的xml可能是要符合一定的XSD的,需要指定順序,@XmlType可以滿足我們的要求,

@XmlType(name = "StudentType", namespace = JaxbTest.default_namespace, propOrder = { "hobbies" ,"birthDay"}) // propOrder表示元素系列化的順序。不能包括父類的字段

有的時候我們需要添加一些擴充的元素和屬性。 首先看一下如何添加擴充的屬性,我們使用@XmlAnyAttribute注解實作,該注解作用的類型必須為Map<QName, String>類型,我們在School中增加 @XmlAnyAttribute //隻要定義擴充屬性,在xsd中為anyAttribute類型 private Map<QName, String> addationInfo; 修改測試代碼如下:

public static void marshallerXML() throws JAXBException,
			DatatypeConfigurationException {
		JAXBContext jc = JAXBContext.newInstance(School.class);
		School school = new School();
		school.setName("實驗");
		school.setAddress("南京雨花台區");
		Map<QName, String> schoolAddation = new HashMap<QName, String>();
		QName nameQname = new QName(extend_namespace, "nature", "extend");
		schoolAddation.put(nameQname, "公立");

		school.setAddationInfo(schoolAddation);
		Marshaller marshaller = jc.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		marshaller.setProperty(Marshaller.JAXB_ENCODING, "GBK");
		marshaller.marshal(school, System.out);
	}

測試結果如下:
<?xml version="1.0" encoding="GBK" standalone="yes"?>
<school name="實驗" extend:nature="公立" xmlns="http://www.ozl.com/test.default" xmlns:extend="http://www.ozl.com/test.extend">
    <address>南京雨花台區</address>
</school>
           
 @XmlAnyElement定義我們可以擴充元素:
我們在School類中增加如下字段:
 @XmlAnyElement private List<Object> anyExtendElements;

同時需要在school中增加一個校長的子節點,起java類如下:
           
@XmlRootElement(name="principal",namespace=JaxbTest.extend_namespace)
public class Principal extends AbstracElement {

	private String description;

	/**
	 * @return the description
	 */
	@XmlElement
	public String getDescription() {
		return description;
	}

	/**
	 * @param description the description to set
	 */
	public void setDescription(String description) {
		this.description = description;
	}
}

此時,我們需要定義個xmlFactory來識别這個類
@XmlRegistry
public class ObjectFactory {

	@XmlElementDecl(name="principal",namespace=JaxbTest.extend_namespace)
	public JAXBElement<Principal> createPrincipal(Principal principal) {
		return new JAXBElement<Principal>(new QName("principal"),
				Principal.class, principal);
	}
}

修改測試代碼如下:
public static void marshallerXML() throws JAXBException,
			DatatypeConfigurationException {
		JAXBContext jc = JAXBContext.newInstance(School.class,
				ObjectFactory.class);
		School school = new School();
		school.setName("實驗");
		school.setAddress("南京雨花台區");
		Principal p = new Principal();
		p.setName("ozl");
		school.getAnyExtendElements().add(p);
		Marshaller marshaller = jc.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		marshaller.setProperty(Marshaller.JAXB_ENCODING, "GBK");
		marshaller.marshal(school, System.out);
	}

測試結果如下:
<?xml version="1.0" encoding="GBK" standalone="yes"?>
<ns2:school name="實驗" xmlns:ns2="http://www.ozl.com/test.default" xmlns:ns3="http://www.ozl.com/test.extend">
    <ns2:address>南京雨花台區</ns2:address>
    <ns3:principal name="ozl"/>
</ns2:school>
           

 這樣就是用jaxb實作了xml和java對象之間的轉換

二、XStream xStream

可以輕易的将Java對象和xml文檔互相轉換,而且也支援json的轉換。我們仍然用School模型來簡要介紹XStream。

public abstract class AbstracElement {

	private String address;
	
	private String name;

	/**
	 * @return the address
	 */
	public String getAddress() {
		return address;
	}

	/**
	 * @param address the address to set
	 */
	public void setAddress(String address) {
		this.address = address;
	}

	/**
	 * @return the name
	 */
	@XmlAttribute
	public String getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}
}
public class Student extends AbstracElement {

	// 出生日期
	private XMLGregorianCalendar birthDay;

	// 星期愛好
	private List<String> hobbies;

	/**
	 * @return the birthDay
	 */
	public XMLGregorianCalendar getBirthDay() {
		return birthDay;
	}

	/**
	 * @param birthDay
	 *            the birthDay to set
	 */
	public void setBirthDay(XMLGregorianCalendar birthDay) {
		this.birthDay = birthDay;
	}

	/**
	 * @return the hobbies
	 */
	public List<String> getHobbies() {
		return hobbies;
	}

	/**
	 * @param hobbies
	 *            the hobbies to set
	 */
	public void setHobbies(List<String> hobbies) {
		this.hobbies = hobbies;
	}
}
public class School extends AbstracElement {

	private List<Student> students;

	private int studentCount;

	/**
	 * @return the students
	 */
	public List<Student> getStudents() {
		return students;
	}

	/**
	 * @param students
	 *            the students to set
	 */
	public void setStudents(List<Student> students) {
		this.students = students;
	}

	/**
	 * @return the studentCount
	 */
	public int getStudentCount() {
		studentCount = getStudents().size();
		return this.studentCount;
	}
}
public class XStreamTest {

	/**
	 * @param args
	 * @throws DatatypeConfigurationException 
	 */
	public static void main(String[] args) throws DatatypeConfigurationException {

		XStream xstream = new XStream();      //預設構造函數需要xpp.jar
		xstream.alias("school", School.class);
		
		xstream.alias("student", Student.class);
		xstream.toXML(createStudent(), System.out);
	}

	public static School createStudent() throws DatatypeConfigurationException
	{
		School school=new School();
		school.setName("實驗");
		school.setAddress("南京雨花台區");
		
		List<Student> students=new ArrayList<Student>();
		Student student1=new Student();
		student1.setName("張三");
		student1.setAddress("南京市xx路");
		List<String> hobbies = new ArrayList<>();
		hobbies.add("遊泳");
		hobbies.add("乒乓球");
		student1.setHobbies(hobbies);
		XMLGregorianCalendar c = DatatypeFactory.newInstance()
				.newXMLGregorianCalendarDate(2014, 11, 11,
						DatatypeConstants.FIELD_UNDEFINED);
		student1.setBirthDay(c);
		students.add(student1);
		
		school.setStudents(students);
		return school;
	}
}
           

 這樣輸入的結果如下:

<school>

     <address>南京雨花台區</address>

       <name>實驗</name>

      <students>

          <student>

                <address>南京市xx路</address>

                <name>張三</name>

                <birthDay class="com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl">

                          <year>2014</year>

                          <month>11</month>

                           <day>11</day>

                           <timezone>-2147483648</timezone>

                           <hour>-2147483648</hour>

                          <minute>-2147483648</minute>

                           <second>-2147483648</second>

                   </birthDay>

                    <hobbies>

                         <string>遊泳</string>

                         <string>乒乓球</string>

                    </hobbies> </student>

                  </students>

             <studentCount>0</studentCount>

</school>

此時我們不需要執行個體化school的studentCount屬性,我們可以在測試代碼中添加如下代碼:

xstream.omitField(School.class, "studentCount");//表示序列号忽略該字段,也可以通過注解@XStreamOmitField

Xstream預設對List類型多了一層包裝,我們可以通過修改如下代碼來去掉<students>包裝:

xstream.addImplicitCollection(School.class, "students");

而我同時需要把name字段作為一個屬性輸出,我們可以添加測試代碼如下: xstream.useAttributeFor(AbstracElement.class, "name");

同樣hobbies我們需要執行個體化一個節點,這個時候我們得提供自定義的序列化方式 xstream.registerLocalConverter(Student.class, "hobbies", new ListXmlConverter()); 其中ListXmlConverter類的代碼如下:

public class ListXmlConverter implements SingleValueConverter {

	/* (non-Javadoc)
	 * @see com.thoughtworks.xstream.converters.ConverterMatcher#canConvert(java.lang.Class)
	 */
	@Override
	public boolean canConvert(Class type) {
		if(List.class.isAssignableFrom(type))
		{
			return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see com.thoughtworks.xstream.converters.SingleValueConverter#toString(java.lang.Object)
	 */
	@Override
	public String toString(Object obj) {
		@SuppressWarnings("unchecked")
		List<String> hobbies=(List<String>) obj;
		StringBuffer buffer=new StringBuffer();
		for(int i=0;i<hobbies.size()-1;i++)
		{
			buffer.append(hobbies.get(i));
			buffer.append("#");
		}
		if(hobbies.size()>0)
		{
			buffer.append(hobbies.get(hobbies.size()-1));
		}
		return buffer.toString();
		}

	/* (non-Javadoc)
	 * @see com.thoughtworks.xstream.converters.SingleValueConverter#fromString(java.lang.String)
	 */
	@Override
	public Object fromString(String str) {

		String[] values =str.split("#");
			return new ArrayList<String>(Arrays.asList(values));
	}

}
           

 同樣我們可以自定義birthday屬性的序列化和反序列化的方式。

xstream.registerLocalConverter(Student.class, "birthDay", new DateValueConverter()); DateValueConverter的代碼如下:

public class DateValueConverter implements Converter {
	@Override
	public boolean canConvert(Class type) {
		if (XMLGregorianCalendar.class.isAssignableFrom(type)) {
			return true;
		}
		return false;
	}

	@Override
	public void marshal(Object source, HierarchicalStreamWriter writer,
			MarshallingContext context) {
		XMLGregorianCalendar birthday = (XMLGregorianCalendar) source;
		writer.setValue(birthday.toString());
	}

	@Override
	public Object unmarshal(HierarchicalStreamReader reader,
			UnmarshallingContext context) {
		String value = reader.getValue();
		SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd");
		try {
			Date date = format.parse(value);
			XMLGregorianCalendar c = DatatypeFactory.newInstance()
					.newXMLGregorianCalendarDate(date.getYear(),
							date.getMonth(), date.getDay(),
							DatatypeConstants.FIELD_UNDEFINED);
			return c;
		} catch (ParseException | DatatypeConfigurationException e) {
			return null;
		}
	}
}
           

 我們會發現每次在序列号birthday字段的時候總是會多個一個class屬性,看着很是不爽。原來是XStream在序列号的時候會判斷字段的類型和實際的類型是否為同一個class。 我們可以通過設定系統别名來去除掉該屬性。增加如下代碼: xstream.aliasSystemAttribute(null, "class");

XStream對命名空間的支援:

并不是所有的XMLDriver都支援命名空間,StaxDriver支援命名空間,我們修改測試代碼如下:

QNameMap qNameMap=new QNameMap();
		qNameMap.registerMapping(new QName("http://test", "school", "test"), "school");
		StaxDriver staxDriver=new StaxDriver(qNameMap);
		XStream xstream = new XStream(staxDriver);
		xstream.alias("school", School.class);
           

 這樣序列号出來結果如下:

<?xml version="1.0" ?><test:school xmlns:test="http://test" name="實驗"><address>南京雨花台區</address><student name="張三"><address>南京市xx路</address><birthDay>2014-11-11</birthDay><hobbies>遊泳#乒乓球</hobbies></student></test:school>實驗

三、比較(個人意見)

1、jaxb是基于xsd的,對擴充屬性和擴充元素命名空間提供了很好的支援

2、jaxb可以是基于屬性(getter/setter)方法,而xstream隻能支援字段。如果需要序列化的類沒有字段(比如所有的東西都存入map中),jaxb可以很輕松的搞定。

3、jaxb是jdk内置的,無需引入額外的jar包