JAXB为子节点添加属性

使用jaxb对于处理结构比较复杂的xml,提供了很好的和Java对象的映射。

在项目中使用了jaxb,但是遇到一个问题,有很多的子节点需要有属性值,如下所示:


	Java
	50.0
	20

上面的xml中的name节点,一般来说是String类型的,这样遇到一个问题,要怎么给这个节点添加id属性?


先说下属性的添加

添加属性使用的注解是@XmlAttribute,例如:

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;

@XmlAccessorType(XmlAccessType.FIELD)
public class Book {

	@XmlAttribute
	private int id;

	private String name;

	private double price;

	private int num;

	public Book() {
	}

	public Book(int id, String name, double price, int num) {
		this.id = id;
		this.name = name;
		this.price = price;
		this.num = num;
	}
}

但是这样添加属性,是在book节点添加名为id的属性,不能添加到name节点上。

实现的结果样例如下:


	Java
	50.0
	20

在网上找了也没有找到合适的办法。后来在查找API的时候,我发现了一个注解@XmlValue,使用这个注解可以解决这个问题。

重新定义Java对象,如下:

Book.java

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

@XmlAccessorType(XmlAccessType.FIELD)
public class Book {

	private Name name;

	private double price;

	private int num;

	public Book() {
	}

	public Book(Name name, double price, int num) {
		this.name = name;
		this.price = price;
		this.num = num;
	}
}

注:修改后的Book类和上面的不同在于,将name属性抽出来定义成了一个类,而不是用String类型。

Name.java

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;

@XmlAccessorType(XmlAccessType.FIELD)
public class Name {

	@XmlAttribute(name = "id")
	private int id;

	@XmlValue
	private String value;	//变量名随意

	public Name() {
	}

	public Name(int id, String value) {
		this.id = id;
		this.value = value;
	}

}

Name类中,将id使用@XmlAttribute注解定义为属性,另外再声明一个String类型的变量,并使用@XmlValue注解来标注。当然,这个变量也可以是String外的其他类型(视情况而定),变量名可以随意。

通过上面的方式,就可以实现文章开头所需要的xml格式。

注:@XmlValue注解在一个类中只能出现一次且不能和@XmlElement同时使用。


这里再记录下使用jaxb中一些细节:

一、

@XmlElement 注解:
该注解用于绑定类中的元素为xml的节点,可用在属性和方法上。
1、name参数,如果指明name参数,会使用该参数的值作为节点名称,如果不用则会自动将变量名作为节点名称。
2、namespace参数,用于指定该节点的命名空间。

二、

@XmlAccessorType 注解的参数:
1、XmlAccessType.PROPERTY 会绑定类中所有的getter/setter方法,而且每个成员变量的getter和setter方法都必须存在。
2、XmlAccessType.FIELD 会绑定类中所有的非静态和没有@XmlTransient注解的成员变量。
3、XmlAccessType.PUBLIC_MEMBER 会绑定类中所有的getter/setter方法和public修饰的成员变量,但是@XmlTransient注解的除外。
4、XmlAccessType.NONE 没有任何变量和方法会被绑定,但是使用@XmlElement和@XmlAttribute的变量和方法还是会被绑定。

注:以上的4中参数中,除了NONE外其他的都会自动绑定成员变量 或者 是getter/setter方法,
这里需要注意,PROPERTY和PUBLIC_MEMBER参数会自动绑定getter/setter方法,而在成员变量上再使用@XmlElement会报错说“类的两个属性具有相同名称”。
同样的,FIELD参数绑定了成员变量,而在getter/setter方法上使用@XmlElement也会报错说“类的两个属性具有相同名称”。
NONE参数虽然不指定任何绑定,但是如果同时在成员变量和getter/setter方法上使用@XmlElement也会报错。
即:不能对同一个变量使用两次绑定。

三、

@XmlElementWrapper 注解:
该注解可用于为Collection或数组的变量声明出一个父节点

@XmlElementWrapper(name = "books")
@XmlElement(name = "book")
private List books;

如果不使用该注解,则生成的xml为:


    Java
    50.0


    HTML
    40.0
使用该注解,生成的xml为:

    
        Java
        50.0
    
    
        HTML
        40.0
    
四、自定义命名空间

有时候需要生成的xml中有自定义的命名空间,在网上可以找到在类的包名上使用@XmlSchema注解,这个方法我没有试过,这里讲另外一种方法,使用com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper接口,jdk不同的版本,该接口的包名路径可能不同,我使用的是jdk1.8。

这里给出我的工具类代码,其中也用到了dom4j用于处理xml文件的流处理。

XMLUtils.java

package com.lk.util;

import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.io.XMLWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

public class XMLUtils {

	public static JAXBContext getJAXBContext(Object obj) throws JAXBException {
		JAXBContext jaxbContext = null;

		return JAXBContext.newInstance(obj.getClass());
	}

	/**
	 * 使用jaxb将对象转换为xml字符串
	 * @param obj
	 * @return
	 */
	public static String objToXML(Object obj) throws JAXBException {
		JAXBContext jaxbContext = getJAXBContext(obj);
		StringWriter writer = new StringWriter();

		Marshaller marshaller = jaxbContext.createMarshaller();
		//设置编码格式
		marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
		//设置否是格式化xml
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		//是否省略头信息
		marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);
		//设置schema约束的命名空间
		marshaller.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
			@Override
			public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
				if (XMLSchemaDict.NAMESPACE_S1.equals(namespaceUri))
					return XMLSchemaDict.NAMESPACE_S1_PREFIX;
				if (XMLSchemaDict.NAMESPACE_S2.equals(namespaceUri))
					return XMLSchemaDict.NAMESPACE_S2_PREFIX;
				return suggestion;
			}
		});
		marshaller.marshal(obj, writer);

		return writer.toString();
	}

	/**
	 * 使用jaxb将字符串转换为对象
	 * @param xmlStr
	 * @param obj
	 * @return
	 */
	public static Object xmlToObj(String xmlStr,Object obj) throws JAXBException {
		JAXBContext jaxbContext = getJAXBContext(obj);

		Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
		StringReader reader = new StringReader(xmlStr);

		return unmarshaller.unmarshal(reader);
	}

	/**
	 * 将xml字符串转为Document对象
	 * @param xmlStr
	 * @return
	 * @throws DocumentException
	 */
	public static Document strToDoc(String xmlStr) throws DocumentException {
		return DocumentHelper.parseText(xmlStr);
	}

	/**
	 * 生产xml文件
	 * @param document
	 * @param path
	 * @throws IOException
	 */
	public static void generatorFile(Document document, String path) throws IOException {
		XMLWriter xmlWriter = new XMLWriter(new FileWriter(path));
		xmlWriter.write(document);
		xmlWriter.close();
	}

	/**
	 * 生产xml文件
	 * @param xmlStr
	 * @param path
	 * @throws IOException
	 */
	public static void generatorFile(String xmlStr, String path) throws IOException, DocumentException {
		Document document = DocumentHelper.parseText(xmlStr);
		XMLWriter xmlWriter = new XMLWriter(new FileWriter(path));
		xmlWriter.write(document);
		xmlWriter.close();
	}
}

注:其中设置命名空间的方法:

marshaller.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
	@Override
	public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
		if (XMLSchemaDict.NAMESPACE_S1.equals(namespaceUri))
			return XMLSchemaDict.NAMESPACE_S1_PREFIX;
		if (XMLSchemaDict.NAMESPACE_S2.equals(namespaceUri))
			return XMLSchemaDict.NAMESPACE_S2_PREFIX;
		return suggestion;
	}
});
要注意"com.sun.xml.internal.bind.namespacePrefixMapper",这个字符串的路径和jdk的版本有很大的关系,原先在网上找到的方法是“com.sun.xml.bind.namespacePrefixMapper”,这个好像是jdk1.6及以前的版本,我运行时这样代码会报错,jdk1.7以后就是我上面的方法中的了,我没有再用1.6另外测试。


其中使用的常量词典类

XMLSchemaDict.java

package com.lk.util;

public class XMLSchemaDict {

	public static final String NAMESPACE_S1 = "http://www.s1.com";
	public static final String NAMESPACE_S1_PREFIX = "s1";
	public static final String NAMESPACE_S2 = "http://www.s2.com";
	public static final String NAMESPACE_S2_PREFIX = "s2";

}

三个完整的实体类(加命名空间)

BookStore.java

package com.lk.entity;

import com.lk.util.XMLSchemaDict;

import javax.xml.bind.annotation.*;
import java.util.List;

@XmlRootElement(name = "BookStore",namespace = XMLSchemaDict.NAMESPACE_S1)
@XmlAccessorType(XmlAccessType.FIELD)
public class BookStore {

	/**
	 * @XmlElementWrapper 注解:
	 * 该注解可用于为Collection或数组的变量声明出一个父节点
	 */
	@XmlElementWrapper(name = "books",namespace = XMLSchemaDict.NAMESPACE_S1)
	@XmlElement(name = "book",namespace = XMLSchemaDict.NAMESPACE_S1)
	private List books;

	public List getBooks() {
		return books;
	}

	public void setBooks(List books) {
		this.books = books;
	}
}
Book.java
package com.lk.entity;

import com.lk.util.XMLSchemaDict;

import javax.xml.bind.annotation.*;

/**
 * @XmlAccessorType 注解的参数:
 * 1、XmlAccessType.PROPERTY 会绑定类中所有的getter/setter方法,而且每个成员变量的getter和setter方法都必须存在。
 * 2、XmlAccessType.FIELD 会绑定类中所有的非静态和没有@XmlTransient注解的成员变量。
 * 3、XmlAccessType.PUBLIC_MEMBER 会绑定类中所有的getter/setter方法和public修饰的成员变量,但是@XmlTransient注解的除外。
 * 4、XmlAccessType.NONE 没有任何变量和方法会被绑定,但是使用@XmlElement和@XmlAttribute的变量和方法还是会被绑定。
 *
 * 注:以上的4中参数中,除了NONE外其他的都会自动绑定成员变量 或者 是getter/setter方法,
 * 这里需要注意,PROPERTY和PUBLIC_MEMBER参数会自动绑定getter/setter方法,而在成员变量上再使用@XmlElement会报错说“类的两个属性具有相同名称”,
 * 同样的,FIELD参数绑定了成员变量,而在getter/setter方法上使用@XmlElement也会报错说“类的两个属性具有相同名称”。
 * NONE参数虽然不指定任何绑定,但是如果同时在成员变量和getter/setter方法上使用@XmlElement也会报错。
 * 即:不能对同一个变量使用两次绑定。
 */
@XmlAccessorType(XmlAccessType.FIELD)
public class Book {

	/**
	 * @XmlElement 注解:
	 * 该注解用于绑定类中的元素为xml的节点,可用在属性和方法上。
	 * 1、name参数,如果指明name参数,会使用该参数的值作为节点名称,如果不用则会自动将变量名作为节点名称。
	 * 2、namespace参数,用于指定该节点的命名空间。
	 *
	 */
	@XmlElement(name = "Name",namespace = XMLSchemaDict.NAMESPACE_S2)
	private Name name;

	@XmlElement(namespace = XMLSchemaDict.NAMESPACE_S2)
	private double price;

	@XmlElement(namespace = XMLSchemaDict.NAMESPACE_S2)
	private int num;

	public Book() {
	}

	public Book(Name name, double price, int num) {
		this.name = name;
		this.price = price;
		this.num = num;
	}

}

Name.java

package com.lk.entity;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;

@XmlAccessorType(XmlAccessType.FIELD)
public class Name {

	@XmlAttribute(name = "id")
	private int id;

	@XmlValue
	private String value;	//变量名随意

	public Name() {
	}

	public Name(int id, String value) {
		this.id = id;
		this.value = value;
	}

}

测试类:

TestJaxb.java

package com.lk.test;

import com.lk.entity.Book;
import com.lk.entity.BookStore;
import com.lk.entity.Name;
import com.lk.util.XMLUtils;
import org.junit.Test;

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

public class TestJaxb {
	@Test
	public void test1() throws Exception {
		BookStore bookStore = new BookStore();
		List list = new ArrayList<>();

		Name name1 = new Name(1, "Java");
		Book book1 = new Book(name1, 50, 20);

		Name name2 = new Name(2, "HTML");
		Book book2 = new Book(name2, 40, 10);

		list.add(book1);
		list.add(book2);

		bookStore.setBooks(list);

		String s = XMLUtils.objToXML(bookStore);
		XMLUtils.generatorFile(s, "d:/books.xml");
	}
}

测试类运行的结果:



    
        
            Java
            50.0
            20
        
        
            HTML
            40.0
            10
        
    
以上是我使用中发现的一些细节,其他的常用功能在网上也很容易找到了。

你可能感兴趣的:(Java)