JAXB为子节点添加属性

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

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


<book>
	<name id="1">Java</name>
	<price>50.0</price>
	<num>20</num>
</book>

上面的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节点上。

实现的结果样例如下:

<book id="1">
	<name>Java</name>
	<price>50.0</price>
	<num>20</num>
</book>

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

重新定义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类型。

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<Book> books;

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


<book>
    <Name id="1">Java</Name>
    <price>50.0</price>
</book>
<book>
    <Name id="2">HTML</Name>
    <price>40.0</price>
</book>

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

<books>
    <book>
        <Name id="1">Java</Name>
        <price>50.0</price>
    </book>
    <book>
        <Name id="2">HTML</Name>
        <price>40.0</price>
    </book>
</books>

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

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

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另外测试。


其中使用的常量词典类
 


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";
 
}

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

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<Book> books;
 
	public List<Book> getBooks() {
		return books;
	}
 
	public void setBooks(List<Book> books) {
		this.books = books;
	}
}
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;
	}
 
}
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;
	}
 
}

测试类:


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<Book> 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");
	}
}

测试类运行的结果:


<?xml version="1.0" encoding="UTF-8"?>
<s1:BookStore xmlns:s1="http://www.s1.com" xmlns:s2="http://www.s2.com">
    <s1:books>
        <s1:book>
            <s2:Name id="1">Java</s2:Name>
            <s2:price>50.0</s2:price>
            <s2:num>20</s2:num>
        </s1:book>
        <s1:book>
            <s2:Name id="2">HTML</s2:Name>
            <s2:price>40.0</s2:price>
            <s2:num>10</s2:num>
        </s1:book>
    </s1:books>
</s1:BookStore>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值