使用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
以上是我使用中发现的一些细节,其他的常用功能在网上也很容易找到了。