用来记录一些原创性的总结
摘要 本文主要通过理论加实践的方式介绍JAXB非常具有代表性的注解。
XmlRootElement用于标注在根节点对应的Java类上。比如上一篇介绍的Person类对应的根节点,我们就在Person类上加上了@XmlRootElement。
@XmlRootElement
public class Person {
//...
}
根节点的名称默认会取类名称的首字母小写。如果默认名称不能满足你的需要,可以通过XmlRootElement的name属性指定根节点的名称。比如下面就是需要生成的根节点名称为person1。
@XmlRootElement(name="person1")
public class Person {
//...
}
如果需要指定namespace,也可以通过它的namespace属性指定。
XmlElement用于标注Java类的属性或get/set方法上,表示对应的属性或get/set方法需要与XML的某一个元素映射。比如有如下这样一段XML:
<root>
<value>Hi</value>
</root>
我们需要把它与我们下面的类映射起来,就可以在对应的getAbc()方法上加上@XmlElement(name=”value”),其实JAXB注解不是只能加在get方法上,加在set方法上也是可以的,尤其是在只有set方法,没有get方法且只需要将XML转换为对象时,此时如果有需要我们就可以在set方法上加上XmlElement注解了。
@XmlRootElement
public class Root {
private String abc;
public void setAbc(String abc) {
this.abc = abc;
}
@XmlElement(name="value")
public String getAbc() {
return this.abc;
}
}
默认情况下一个属性及其对应的get/set方法没有添加任何JAXB注解的时候,该属性也会被自动的与它同名的XML元素进行绑定。所以通常XmlElement用于属性名称与XML元素名称不一致的情况下通过name属性指定需要绑定的XML元素的名称。 除了指定name属性外,我们还可以指定namespace、nilable、required、defaultValue等属性,这些通常用于基于Java类生成对应的XML Schema的情形。
XmlAttribute用于映射XML元素属性的,默认的属性名称与Java类的属性名称一致,可以通过name属性指定属性名称。比如下面这个类我们在对应的getNo()方法上加上了@XmlAttribute,表示它将作为Root类对应XML元素上的一个名为no的属性,而getName()上的@XmlAttribute通过name属性指定了它对应的XML属性名是name1。
@XmlRootElement
public class Root {
private String no;
private String name;
public void setNo(String no) {
this.no = no;
}
@XmlAttribute
public String getNo() {
return this.no;
}
public void setName(String name) {
this.name = name;
}
@XmlAttribute(name="name1")
public String getName() {
return this.name;
}
}
生成的XML是类似如下这样的:
<root name1="AAA" no="A1"/>
除了name属性外,XmlAttribute还可以选择性的指定namespace和required属性。
XmlValue是用于映射直接应用XML元素的文本内容的。比如上面的XML节点root,如果其中还包含有文本内容,如:<root name1="AAA" no="A1">Text Content</root>
,我们就可以在Root类中添加一个属性并使用@XmlValue标注,那么该属性的值就对应于root节点的文本内容。
@XmlRootElement
public class Root {
private String no;
private String name;
private String value;
public void setNo(String no) {
this.no = no;
}
@XmlAttribute
public String getNo() {
return this.no;
}
public void setName(String name) {
this.name = name;
}
@XmlAttribute(name="name1")
public String getName() {
return this.name;
}
public void setValue(String value) {
this.value = value;
}
@XmlValue
public String getValue() {
return this.value;
}
}
XmlType用于定义在类上,表示该类对应于XML的一个复杂类型对象。默认情况下不加XmlType时我们的类也会自动的被映射为XML的一个复杂类型对象,对应的XML类型默认是Java类名称的首字母小写的形式,可以如果不需要使用默认名称时可以通过name属性指定。通常我们在一个类上使用@XmlType时主要是希望通过它的propOrder属性指定生成的XML的属性排列顺序,不指定顺序时默认生成的XML的节点/属性的顺序是不确定的。
@XmlRootElement
@XmlType(propOrder= {"no", "name", "value"})
public class Root {
private String no;
private String name;
private String value;
public void setNo(String no) {
this.no = no;
}
public String getNo() {
return this.no;
}
public void setName(String name) {
this.name = name;
}
@XmlElement(name="name1")
public String getName() {
return this.name;
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
上面的示例中我们就通过XmlType的propOrder指定了属性的排列顺序为no、name、value,对应的值需要是Java类的属性名。生成的XML是类似如下这样的:
<root>
<no>A1</no>
<name1>AAA</name1>
<value>ABCDEFG</value>
</root>
除了name和propOrder属性外,也可以通过namespace属性指定namespace。如果对应的类型的对象是有某个工厂方法产生的,需要是静态的无参数的方法,我们可以通过factoryClass和factoryMethod指定在进行XML转Java对象创建对应类型的对象的工厂类和工厂方法。
XmlAccessorOrder也是指定Java对象生成的XML元素/属性的顺序的。通过它指定的顺序的可选值有:
比如上面的排序如果期望是按照字母的自然顺序排列,则可以如下这样定义:
@XmlRootElement
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public class Root {
//...
}
XmlAccessorOrder除了可以定义在类上,还可以定义在包上,定义在包上表示在该包中所有的类型对应的XML的默认排序规则。定义在包中时需要在对应的包下面建立package-info.java文件,然后将该注解加在package-info.java中的package声明上。如下我们就在com.xxx.jaxb的package-info.java中指定了com.xxx.jaxb包中的类对应的XML元素顺序默认按照字母升序排列。
@javax.xml.bind.annotation.XmlAccessorOrder(javax.xml.bind.annotation.XmlAccessOrder.ALPHABETICAL)
package com.xxx.jaxb;
XmlTransient用于在进行Java对象和XML相互转换时定义需要忽略的Java属性。比如下面的Root类中有no、name和value三个属性,value属性在系统中有其它作用,但是在转换Root对象为XML时我们不期望value属性也作为其中的一部分,就在对应的getValue()方法上加上了@XmlTransient。
@XmlRootElement
public class Root {
private String no;
private String name;
private String value;
public void setNo(String no) {
this.no = no;
}
public String getNo() {
return this.no;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setValue(String value) {
this.value = value;
}
@XmlTransient
public String getValue() {
return this.value;
}
}
XmlAccessorType也是JAXB中比较常用的一个注解,可以标注在Class和package上,标注在package上时需要标注在对应的package-info.java文件的package上。XmlAccessorType的作用是指定Java对象和XML相互转换时Java对象属性访问方式,即哪些属性会与XML进行映射。它的可选值是由XmlAccessType类型的枚举指定。一共支持四种类型,无论配置的是哪种类型,使用JAXB注解的都将会自动映射。
如下代码中我们指定的XmlAccessorType是FIELD,那么在生成XML时就将以Field进行映射,Root类是没有get/set方法的。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
private String no = "no1";
private String name = "Name1";
private String value = "Value1";
}
生成的XML如下所示:
<root>
<name>Name1</name>
<no>no1</no>
<value>Value1</value>
</root>
如果package和Class上同时拥有XmlAccessorType定义时将以Class上的定义为准。
XmlElementWrapper用于进行集合类型的属性映射时,在XML元素的外层再多包一层元素。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
private List<String> values = Arrays.asList("A", "B", "C");
}
上面这个Java类的对象生成的XML是如下这样:
<root>
<values>A</values>
<values>B</values>
<values>C</values>
</root>
这是因为对于集合类型的属性,JAXB内部会自动遍历其中的每一个元素把它们生成XML,元素的名称默认是属性名称,即上面的values,如果我们希望生成的元素名称是其它的,比如是value,则可以通过@XmlElement(name=”value”)来指定。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlElement(name="value")
private List<String> values = Arrays.asList("A", "B", "C");
}
这样生成的XML就是如下这样:
<root>
<value>A</value>
<value>B</value>
<value>C</value>
</root>
如果我们希望上面的每一个value元素都可以被一个元素包裹起来,比如values,则可以标注@XmlElementWrapper(name=”values”),通过name属性指定包裹的元素的名称,没有指定name属性时包裹的元素的名称默认会取对应的Java属性的名称。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlElementWrapper
@XmlElement(name="value")
private List<String> values = Arrays.asList("A", "B", "C");
}
加上了@XmlElementWrapper之后生成的XML是如下这样:
<root>
<items>
<value>A</value>
<value>B</value>
<value>C</value>
</items>
</root>
XmlElementWrapper除了可以指定name属性外,还可以指定namespace、required和nilable属性。
XmlJavaTypeAdapter可以用于在进行Java对象和XML相互转换时做一些适配工作,比如需要把java.util.Date转换为XML的字符串形式的yyyy-MM-dd格式。使用XmlJavaTypeAdapter时需要通过value属性指定一个XmlAdapter类,表示对应的适配器类。XmlAdapter是一个抽象类。其中定义了两个方法,marshal和unmarshal,marshal方法用于适配从Java到XML,unmarshal方法用于适配从XML到Java。比如需要把java.util.Date类型转换为yyyy-MM-dd格式的字符串可以定义如下适配器。
public class DateAdapter extends XmlAdapter<String, java.util.Date> {
private static final String PATTERN = "yyyy-MM-dd";
@Override
public Date unmarshal(String v) throws Exception {
if (v != null) {
return new SimpleDateFormat(PATTERN).parse(v);
}
return null;
}
@Override
public String marshal(Date v) throws Exception {
if (v != null) {
return new SimpleDateFormat(PATTERN).format(v);
}
return null;
}
}
适配器实现时注意考虑值为null的情形。
在需要使用适配器的属性或get/set方法上可以通过@XmlJavaTypeAdapter指定需要使用的适配器,比如下面代码就是使用我们刚刚定义的DateAdapter适配器。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlJavaTypeAdapter(DateAdapter.class)
private Date date = new Date();
}
生成的XML如下:
<root>
<date>2017-11-19</date>
</root>
XmlJavaTypeAdapter也可以标注在Java类上,表示遇到对应的类型时就使用指定的适配器,比如下面的代码就会在遇到MyDate类型时自动使用DateAdapter。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
private MyDate date = new MyDate();
private MyDate date2 = new MyDate();
@XmlJavaTypeAdapter(DateAdapter.class)
public static class MyDate extends Date {
}
}
XmlJavaTypeAdapter也可以标注在package上,标注在package上时必须指定type类型,表示在指定的包中遇到type属性指定的类型时就使用value属性对应的适配器。下面的代码表示在com.xxx.jaxb中遇到java.util.Date类型就使用DateAdapter适配器。
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value=DateAdapter.class, type=java.util.Date.class)
package com.xxx.jaxb;
XmlJavaTypeAdapters只能标注在package上,是用于定义多个XmlJavaTypeAdapters的。比如下面就定义了在包com.xxx.jaxb中遇到了java.util.Date类型就使用DateAdapter适配器,遇到了java.math.BigDecimal类型就使用MoneyAdapter适配器。
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters({
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value = DateAdapter.class, type = java.util.Date.class),
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value = MoneyAdapter.class, type = java.math.BigDecimal.class) })
package com.xxx.jaxb;
考虑现在有一段XML,其基本结构如下,root是节点下有固定的no和name节点,其它节点不固定,而且可能性很多,穷举非常麻烦。这种场景下我们就可以使用XmlAnyElement来映射了,它可以映射除固定的映射以外的所有其它XML节点。如果需要动态映射的节点确定只有一个,我们可以使用一个Object或org.w3c.dom.Element类型的属性来接收。如果需要动态映射的节点数是不确定的,则需要使用一个List来接收。
<root>
<no>A01</no>
<name>A</name>
<d1>D</d1>
<d2>D</d2>
</root>
比如上面的示例我们就可以使用一个List来接收,默认创建的都是org.w3c.dom.Element类型的对象。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
public String no;
public String name;
@XmlAnyElement
public List<Object> others;
}
如果动态的节点有些是一种固定的类型,我们可以在使用@XmlAnyElement时指定其lax属性为true。这会让JAXB在把XML转换为Java对象时直接把它们转换为对应的对象。比如上面的XML可能出现的d1节点有一个匹配的Java类D1,其代码如下:
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
public String no;
public String name;
@XmlAnyElement(lax=true)
public List<Object> others;
@XmlRootElement(name="d1")
@XmlAccessorType(XmlAccessType.FIELD)
public static class D1 {
@XmlValue
public String value;
}
}
这样进行了unmarshal之后对应的others属性中就包含了一个D1类型的对象了,单元测试如下:
@Test
public void testBasicUnmarshal() throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Root.class, D1.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Root root = (Root) unmarshaller.unmarshal(this.getClass().getClassLoader().getResourceAsStream("jaxb/dynamic.xml"));
D1 d1 = null;
for (Object obj : root.others) {
if (obj instanceof D1) {
d1 = (D1) obj;
}
}
Assert.assertNotNull(d1);
}
XmlAnyAttribute用于映射除已知属性以外的任意属性,它对应于Java类里面一个Map类型的属性,Map的Key需要是javax.xml.namespace.QName
类型的。假设有类似下面这样一段XML需要应用动态的属性匹配。
<holder prop1="value1" prop2="value2" prop3="value3"/>
可以建立类似下面这样的代码进行匹配:
@XmlRootElement(name="holder")
@XmlAccessorType(XmlAccessType.FIELD)
public class AnyAttributeHolder {
@XmlAnyAttribute
public Map<QName, String> attrs;
}
可以进行验证如下:
@Test
public void test2() {
String xml = "<holder prop1=\"value1\" prop2=\"value2\" prop3=\"value3\"/>";
AnyAttributeHolder holder = JAXB.unmarshal(new StringReader(xml), AnyAttributeHolder.class);
Assert.assertTrue(holder.attrs.get(new QName("prop1")).equals("value1"));
Assert.assertTrue(holder.attrs.get(new QName("prop2")).equals("value2"));
Assert.assertTrue(holder.attrs.get(new QName("prop3")).equals("value3"));
}
XmlElements是用于定义多个@XmlElement的,用于标注在集合类型的属性上以根据集合元素类型给定不同的元素名称。比如下面这段代码我们就定义了集合objs中的String类型的元素对应的XML节点名是string,Integer类型的元素对应的XML节点名是int等。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlElementWrapper(name = "objs")
@XmlElements({ @XmlElement(name = "string", type = String.class), @XmlElement(name = "int", type = Integer.class),
@XmlElement(name = "boolean", type = Boolean.class), @XmlElement(name = "long", type = Long.class) })
public List<Object> objs = Arrays.asList("A", 1, true, 1L);
}
上面Root类型的对象转换为XML后会是如下这样的结果:
<root>
<objs>
<string>A</string>
<int>1</int>
<boolean>true</boolean>
<long>1</long>
</objs>
</root>
XmlElementRef的用途是在属性定义类型是父类,实际传递值是子类的情况下,以子类的结构生成对应的XML。比如下面示例中Root类有一个Parent类型的属性p,其实际值是子类型Sub的实例。我们在p属性上加上了@XmlElementRef,那么在生成XML时就将以子类型Sub的结构为准。此时子类型上必须加上@XmlRootElement注解,对应的节点名称也将根据子类型上的@XmlRootElement定义的为准。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlElementRef
public Parent p = new Sub();
public static class Parent {
public int p1 = 1;
}
@XmlRootElement
public static class Sub extends Parent {
public int s1 = 2;
}
}
在进行转换时所构建的JAXBContext中必须包含子类型的信息,否则子类型对上下文是未知的,比如下面指定的Root.Sub.class,因为直接根据类Root是没有关联的Sub类的信息的。
@Test
public void testBasicMarshal() throws Exception {
Root root = new Root();
JAXBContext jaxbContext = JAXBContext.newInstance(Root.class, Root.Sub.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
上面示例生成的XML如下:
<root>
<sub>
<p1>1</p1>
<s1>2</s1>
</sub>
</root>
XmlElementRef的另一种用法是引用ObjectFactory中定义的类型,这种用法在后文介绍ObjectFactory时介绍。
XmlElementRefs用于定义多个@XmlElementRef,用于集合属性上。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlElementRefs({@XmlElementRef(type=Parent.class)})
public List<Object> objs = Arrays.asList(new Sub(), new Sub());
public static class Parent {
public int p1 = 1;
}
@XmlRootElement
public static class Sub extends Parent {
public int s1 = 2;
}
}
XmlRegistry用于标注在充当ObjectFactory角色的类上,ObjectFactory类型的类里面可以定义一些创建某种类型的对象的方法,其上可以使用@XmlElementDecl声明对应的元素定义,方法的返回值需要是JAXBElement类型,定义了该声明的元素可以通过XmlElementRef引用。如下示例中ObjectFactory类中的createUser方法通过@XmlElementDecl(name=”userEle”)声明了名为userEle的元素定义,其对应JAXBElement的值类型是User类型。Root的user属性通过@XmlElementRef(name=”userEle”)指定了其关联的是ObjectFactory中name为userEle的@XmlElementDecl声明的元素。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlElementRef(name="userEle")
public JAXBElement<User> user;
@XmlRegistry
public static class ObjectFactory {
@XmlElementDecl(name="userEle")
public JAXBElement<User> createUser(User user) {
QName qname = new QName("user");
JAXBElement<User> element = new JAXBElement<>(qname, User.class, user);
return element;
}
}
public static class User {
public String name = "user1";
}
}
使用此种方式在创建JAXBContext时传入的Class需要加入对应的ObjectFactory类。
@Test
public void testBasicMarshal() throws Exception {
Root root = new Root();
JAXBContext jaxbContext = JAXBContext.newInstance(Root.class, Root.ObjectFactory.class);
JAXBElement<User> userEle = new Root.ObjectFactory().createUser(new Root.User());
root.user = userEle;
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
上述示例生成的XML如下:
<root>
<user>
<name>user1</name>
</user>
</root>
XmlID用于指定一个类中的某个属性为其唯一标识,对应的属性类型必须是String,且在一个类中只能有一个属性使用@XmlID注解;XmlIDREF用于指定在把一个对象的某个复杂类型的属性转化为XML时,不是直接转换整个对象的结构,而是转换其对应的XmlID。
@XmlAccessorType(XmlAccessType.FIELD)
public static class Product {
@XmlID
private String id = "1";
private String name = "Apple";
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public static class Order {
private Integer id = 1;
@XmlIDREF
private Product product = new Product();
private Integer num = 10;
}
上面的java类如果我们把Order对象转换为XML,那么product属性关联的将是对应的Product对象的id属性,而不是整个Product对象。结果如下:
<order>
<id>1</id>
<num>10</num>
<product>1</product>
</order>
XmlList用于把基本数据类型的集合对象的内容都生成到一个元素中,每个元素间以逗号分隔。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlList
private List<String> strs = Arrays.asList("A", "B", "C");
}
比如上面这段配置会生成如下XML:
<root>
<strs>A B C</strs>
</root>
如果把strs上的@XmlList去掉,则会生成如下XML:
<root>
<strs>A</strs>
<strs>B</strs>
<strs>C</strs>
</root>
XmlSeeAlso用于指定相关的Class,使其在创建JAXBContext时能够自动被JAXBContext识别。如下示例在创建JAXBContext时只需要传入Root类,其中动态引入到的User类型的对象也能被JAXBContext识别,因为Root类上已经通过@XmlSeeAlso引入了User类。
@XmlRootElement
@XmlSeeAlso(User.class)
public class Root {
public static class User {
}
}
@XmlEnum用于标注在枚举类上,表示其是一个枚举类型。如果你希望生成的Schema中对应的枚举值的定义不是String类型的,则可以通过@XmlEnum的value属性指定类型,比如是int,则可以指定为@XmlEnum(int.class)。@XmlEnumValue用来指定枚举元素值与XML绑定的值。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
private Color color = Color.BLUE;
@XmlEnum(int.class)
public static enum Color {
@XmlEnumValue("10")
RED,
@XmlEnumValue("20")
BLUE,
@XmlEnumValue("30")
YELLOW;
}
}
上面配置的Root类型的对象在转换为XML时会是如下结果,color的值是通过@XmlEnumValue指定的值,而不是枚举元素的字符串表示(默认会是字符串表示)。
<root>
<color>20</color>
</root>