JAXB注解的使用详解

时间:2024-03-27 21:05:56

前言:

  最近一直在做各种接口的对接,接触最多的数据类型就是JSON和XML数据,还有XML中包含JSON的数据,而在Java中对象和XML之间的转换经常用到JAXB注解,抽空在这里总结一下,首先做一下准备工作

  测试类代码:

@XmlRootElement
public class Student { private String name; // 姓名
private String sex; // 性别
private int number; // 学号
private String className; // 班级 public Student(){} public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3; } @XmlElement(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} @XmlElement(name = "sex")
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
} @XmlElement(name = "number")
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
} @XmlElement(name = "className")
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
} }

  工具类代码:

public class XStreamUtil {

    /**
* 扩展xstream,使其支持CDATA块
* 整数和浮点数不添加
* @date 2013-05-19
*/
public static XStream xstream2 = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true; @SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
if(!name.equals("xml")){
char[] arr = name.toCharArray();
if (arr[0] >= 'a' && arr[0] <= 'z') {
//arr[0] -= 'a' - 'A';
//ASCII码,大写字母和小写字符之间数值上差32
arr[0] = (char) ((int) arr[0] - 32);
}
name = new String(arr);//char数组转字符串
}
super.startNode(name, clazz);
} @Override
public void setValue(String text) {
if(text!=null && !"".equals(text)){
if(text.matches("[0-9]*(\\.?)[0-9]*") || text.matches("[0-9]*(\\.?)[0-9]*")){//如果是正式或者浮点数
cdata = false;
}else{
cdata = true;
}
}
super.setValue(text);
} protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
}); /**
* 扩展xstream,使其支持CDATA块
*
* @date 2013-05-19
*/
public static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true; @SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
}); /**
* 将XML内容转换成对象
*/
@SuppressWarnings("unchecked")
public static <T> T unmarshal(String xml, Class<T> clazz) throws JAXBException{
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return (T)unmarshaller.unmarshal(new StringReader(xml));
} /**
* 将对象转换成XML
*/
public static String marshal(Object object, Class<?> clazz) throws JAXBException{
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
StringWriter writer = new StringWriter();
marshaller.marshal(object, writer);
return writer.toString();
} /**
* 将java对象转换为xml
* @param <T>
* @param reqTextMessage
* @return
*/
public static <T> String JavaToXml(T t){
xstream.alias("xml", t.getClass());
return xstream.toXML(t);
} }

  测试代码:

public class Test {

    public static void main(String[] args) throws JAXBException {
Student st = new Student("张三","男",10001,"尖");
String xml = XStreamUtil.marshal(st, Student.class);
System.out.println(StringUtils.formatXml(xml));
}
}

一、@XmlRootElement:

  类级别的注解,将类映射为xml全局元素,也就是根元素。就像spring配置文件中的beans

  实例代码:

@XmlRootElement
public class Student { private String name; // 姓名
private String sex; // 性别
private int number; // 学号
private String className; // 班级 public Student(){} public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3; } //省略下面代码

  测试结果:

<?xml version="1.0" encoding="gb2312"?>
<student>
<className>尖</className>
<name>张三</name>
<number>0</number>
<sex>男</sex>
</student> Process finished with exit code 0

二、@XmlAccessorType :

  包和类级别的注解,javaEE的API对该注解的解释是:控制字段是否被默认序列化。通俗来讲,就是决定哪些字段或哪些get/set方法对应的字段会被映射为xml元素,需要注意的是字段或get/set方法的访问权限(public/private)会影响字段是否被映射为xml元素,下面会详细讲解。
  注解只有一个value属性,可取的值是一个名为XmlAccessType的枚举类型里的值,下面详细看一下这几个值分别有什么用:

    2.1、XmlAccessType.PROPERTY:

      理解:下面是源码里面的一段描述,这一段的大致翻译是,《JAXB绑定类中的每个getter/setter对都将自动绑定到XML,除非由@link xmltinate注释,只有当字段被一些JAXB注释显式注释时,字段才绑定到XML》(注:这一段话需要格外注意的是,,该属性可以自动将每个getter/setter绑定到xml,但不会自动绑定字段到XML)

/**
* Every getter/setter pair in a JAXB-bound class will be automatically
* bound to XML, unless annotated by {@link XmlTransient}.
*
* Fields are bound to XML only when they are explicitly annotated
* by some of the JAXB annotations.
*/

      补充:

        (1)当使用了该值,只要字段有对应的get/set方法对(注意是成对出现,只有其中一个不会发生映射),不需要使用@XmlElement注解,不论该方法的访问权限是什么(即使是private),jaxb就会将该字段映射成xml元素。不过最好加上@XmlElement注解,get/set方法任选一个即可,都加上会报错。

        (2)若在一个字段有set/get方法对但又在字段上添加@XmlElement注解会报属性重复的错误

        (3)若没有set/get方法对,则需要在字段上使用@XmlElement注解才可以映射为xml元素,否则不会发生映射

        (4)若get/set方法上使用了@XmlTransient注解,但想要对应字段发生映射,需要在对应字段上添加@XmlElement注解,此时不会报错,并将该字段映射为xml元素。

    2.2、XmlAccessType.FIELD:

      理解:下面是源码中的解释,这一段的大致翻译是,《jaxb绑定类中的每个非静态、非瞬态字段都将自动绑定到XML,除非使用@XmlTransient进行注释,只有当某些JAXB注释显式地对getter/setter对进行注释时,它们才会绑定到XML》(注:这段需要注意的是,该属性可以自动绑定类中的非静态、非瞬态字段,但不会自动绑定getter/setter方法,正好与XmlAccessType.PROPERTY相反

 /**
* Every non static, non transient field in a JAXB-bound class will be automatically
* bound to XML, unless annotated by {@link XmlTransient}.
*
* Getter/setter pairs are bound to XML only when they are explicitly annotated
* by some of the JAXB annotations.
*/

      补充:

        (1)每个非静态的字段(无论访问权限如何)都会被jaxb映射为xml元素,即使没有get/set方法对,即使没有使用@XmlElement元素,但最好加上该注解以表明该字段要被映射为xml元素

        (2)虽然没有get/set方法对,也会发生映射,但加上get/set方法对也不会报错,因为我们经常会使用这两个方法。但注意,不能再在这两个方法上使用@XmlElement方法,否则会报属性重复的错误。

        (3)若在字段上使用了@XmlTransient注解,但还想让该字段发生映射,需要在该字段对应的get/set方法上添加@XmlElement

    2.3、XmlAccessType.PUBLIC_MEMBER (该值为默认值):

      注:如果不指定@XmlAccessorType的value值或者没有使用此注解,在转xml时会默认为value为XmlAccessType.PROPERTY,由下面的截图可以清晰的看出默认值是XmlAccessType.PROPERTY,     

@Inherited @Retention(RUNTIME) @Target({PACKAGE, TYPE})
public @interface XmlAccessorType { /**
* Specifies whether fields or properties are serialized.
*
* @see XmlAccessType
*/
XmlAccessType value() default XmlAccessType.PUBLIC_MEMBER;
}

      理解:下面则是对此的描述翻译大概是《每个公共getter/setter对和每个公共字段都将自动绑定到XML,除非由@link xmltinate批注,私有、受保护或、默认为“仅包访问”仅在以下情况下绑定到XML,由适当的JAXB注释显式注释。》(注:在这里要格外注意XmlAccessType.PUBLIC_MEMBER属性针对的是java对象中所有的public访问权限的成员变量和通过getter/setter方式访问的成员变量,而私有或者仅包访问权限的字段,并不会自动绑定到XML)          

/**
* Every public getter/setter pair and every public field will be
* automatically bound to XML, unless annotated by {@link XmlTransient}.
*
* Fields or getter/setter pairs that are private, protected, or
* defaulted to package-only access are bound to XML only when they are
* explicitly annotated by the appropriate JAXB annotations.
*/

      补充:

        (1)每个访问权限为public的字段,或者每个访问权限为public的get/set方法对,都会将字段映射为xml元素,即使不使用@XmlElement,但最好加上。不可同时存在public字段和对应的get/set方法对,不然会报属性重复的错误

        (2)若使用@XmlElement注解,则实体类(注:实体类中的字段为私有private)中不能存在get/set方法或者只能在get/set上使用,否则会报属性重复的错误

        (3)若字段不为public,get/set方法为public并使用了@XmlTransient,需要在字段上添加@XmlElement才会发生映射,若字段为public并使用了@XmlTransient,get/set方法对不为public,需要在get/set方法上使用@XmlElement才会映射。

    2.4、XmlAccessType.NONE:

      注: 这一段的翻译是《任何字段或属性都不会绑定到XML,除非使用某些JAXB注释对它们进行特别注释。》

 /**
* None of the fields or properties is bound to XML unless they
* are specifically annotated with some of the JAXB annotations.
*/

      补充:

        任何字段,get/set方法对都不会发生映射,除非使用某些注解,如@XmlElement,@XmlElementWrapper等。

三、@XmlElement:

  字段,方法,参数级别的注解。该注解可以将被注解的字段(非静态),或者被注解的get/set方法对应的字段映射为本地元素,也就是子元素。默认使用字段名或get/set方法去掉前缀剩下部分小写作为元素名(在字段名和get/set方法符合命名规范的情况下)。

  属性:该注解的属性常用的属性有如下

    (1)defaultValue:可以指定该元素默认的文本值

    (2)namespace:可以指定该元素所属的命名空间

    (3)name: 同@XmlRootElement注解的name属性一样

    (4)required:可以指定该元素是否必须出现,默认为false

    (5)nillable: 可以指定元素的文本值是否可以为空,默认为false

@XmlRootElement(name = "Student")
@XmlAccessorType(XmlAccessType.FIELD)
public class Student {
@XmlElement(name = "name",defaultValue = "hefeng")
private String name; // 姓名
@XmlElement(name = "sex", namespace = "Student")
private String sex; // 性别
@XmlElement(name = "number", required = true)
private int number; // 学号
@XmlElement(name = "className", nillable = true)
private String className; // 班级 public Student(){} public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3; }//后面省略

  测试结果:

<?xml version="1.0" encoding="gb2312"?>
<Student xmlns:ns2="Student">
<name>张三</name>
<ns2:sex>男</ns2:sex>
<number></number>
<className>尖</className>
</Student>

        

四、@XmlAttribute:

  字段和方法级别的注解。该注解会将字段或get/set方法对应的字段映射成本类对应元素的属性,属性名默认使用字段名或get/set方法去掉前缀剩下部分首字母小写(在字段名和get/set方法符合命名规范的情况下)。修改上面例子:

  属性:该注解有name,required,namespace三个属性。用法和@XmlElement注解相同

@XmlRootElement(name = "Student")
@XmlAccessorType()
public class Student {
private String name; // 姓名
private String sex; // 性别
private int number; // 学号
private String className; // 班级 public Student(){} public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3; } @XmlAttribute(name="shiqingxue")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}

  测试结果:

<Student shiqingxue="张三">
<className>尖</className>
<number>0</number>
<sex>男</sex>
</Student>

五、@XmlAccessorOrder:

  包和类级别的注解。控制生成元素的顺序。

  属性:该属性有XmlAccessOrder.ALPHABETICAL 和 XmlAccessOrder.UNDEFINED两种

    (1)XmlAccessOrder.ALPHABETICAL,代表按照字母表的顺序对生成的元素排序,也就是我们常说的字典顺序

    (2)XmlAccessOrder.UNDEFINED,代表按照类中字段的顺序生成元素的顺序,也是该注解的默认值

  测试XmlAccessOrder.UNDEFINED

@XmlRootElement(name = "Student")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlAccessorOrder()
public class Student {
@XmlElement(name = "NAME")
private String name; // 姓名
@XmlElement(name = "SEX")
private String sex; // 性别
@XmlElement(name = "NUMBER")
private int number; // 学号
@XmlElement(name = "CLASS_NAME")
private String className; // 班级 public Student(){} public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3; }//后面代码省略……

  测试结果:可以看出字段的输出顺序是按照类中字段和属性的顺序

<?xml version="1.0" encoding="gb2312"?>
<Student>
<NAME>张三</NAME>
<SEX>男</SEX>
<NUMBER>0</NUMBER>
<CLASS_NAME>尖</CLASS_NAME>
</Student>

  测试XmlAccessOrder.ALPHABETICAL:

@XmlRootElement(name = "Student")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public class Student {
@XmlElement(name = "NAME")
private String name; // 姓名
@XmlElement(name = "SEX")
private String sex; // 性别
@XmlElement(name = "NUMBER")
private int number; // 学号
@XmlElement(name = "CLASS_NAME")
private String className; // 班级 public Student(){} public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3; }//后面代码省略……

  测试结果:可以看出字段的输出是按照字典顺序排序的

<?xml version="1.0" encoding="gb2312"?>
<Student>
<CLASS_NAME>尖</CLASS_NAME>
<NAME>张三</NAME>
<NUMBER>0</NUMBER>
<SEX>男</SEX>
</Student>

六、@XmlElementWrapper

  字段和方法级别的注解:围绕被映射的xml元素生成包装元素。主要用在集合对象映射后生成包装映射结果的xml元素,来看一下例子,创建两个类Children、Father。

@XmlAccessorType(XmlAccessType.FIELD)
public class Children{
@XmlElement(name = "USER_NAME")
private String userName; public Children(){}; public Children(String userName){
this.userName = userName;
} public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
}
}
@XmlRootElement(name = "Father")
@XmlAccessorType(XmlAccessType.FIELD)
public class Father { @XmlElement(name = "AGE")
private Integer age; @XmlElement(name = "Children")
private List<Children> childrenList = new ArrayList<Children>(); public Father(){} public Father(Integer age){
this.age = age;
childrenList.add(new Children("逝清雪"));
childrenList.add(new Children("莫问"));
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
}
}

  测试代码:

public class Test {

    public static void main(String[] args) throws JAXBException {
Father st = new Father(12);
String xml = XStreamUtil.marshal(st, Father.class);
System.out.println(StringUtils.formatXml(xml));
}
}

  在没有@XmlElementWrapper注解下的测试结果:

<?xml version="1.0" encoding="gb2312"?>
<Father>
<AGE>12</AGE>
<Children>
<USER_NAME>逝清雪</USER_NAME>
</Children>
<Children>
<USER_NAME>莫问</USER_NAME>
</Children>
</Father>

  有@XmlElementWrapper注解下的测试结果:

<?xml version="1.0" encoding="gb2312"?>
<Father>
<AGE>12</AGE>
<List>
<Children>
<USER_NAME>逝清雪</USER_NAME>
</Children>
<Children>
<USER_NAME>莫问</USER_NAME>
</Children>
</List>
</Father>

七、@XmlJavaTypeAdapter

  包、类、字段,方法、参数级别的注解:解决java日期(Date),数字(Number)格式化问题。直接看例子,修改Person类,添加一个Date类型字段:

  在这里要向使用@XmlJavaTypeAdapter我们就要指定一个指向将值类型转换为绑定类型的类,这个类需要继承XmlAdapter抽象类重写里面的unmarshal和marshal方法

  DateAdapter工具类:

public class DateAdapter extends XmlAdapter<String, Date> {

    private SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override
public String marshal(Date v) throws Exception {
return dateFormat.format(v);
} @Override
public Date unmarshal(String v) throws Exception {
return dateFormat.parse(v);
} }

  测试代码:

@XmlRootElement(name = "Children")
@XmlAccessorType(XmlAccessType.FIELD)
public class Children{
@XmlElement(name = "USER_NAME")
private String userName; @XmlJavaTypeAdapter(DateAdapter.class)
@XmlElement(name = "system_date")
private Date systemDate; public Children(){}; public Children(String userName, Date data){
this.userName = userName;
this.systemDate = data;
} public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
} public Date getSystemDate() {
return systemDate;
} public void setSystemDate(Date systemDate) {
this.systemDate = systemDate;
}
}

  测试代码:

public class Test {

    public static void main(String[] args) throws JAXBException {
Children st = new Children("逝清雪", new Date());
String xml = XStreamUtil.marshal(st, Children.class);
System.out.println(StringUtils.formatXml(xml));
}
}

  测试结果:

<?xml version="1.0" encoding="gb2312"?>
<Children>
<USER_NAME>逝清雪</USER_NAME>
<system_date>2019-04-11 11:19:24</system_date>
</Children>

     

 八、@XmlTransient:

  类,字段,方法级别的注解:可使JAXB在映射xml元素时忽略被注解的类,字段,get/set对应字段。需要注意的是该注解与所有其他JAXB注释相互排斥,也就是说与其他注释连用就会报错

@XmlRootElement(name = "Children")
@XmlAccessorType(XmlAccessType.FIELD)
public class Children{
@XmlElement(name = "USER_NAME")
private String userName;
@XmlTransient
private Date systemDate; public Children(){}; public Children(String userName, Date data){
this.userName = userName;
this.systemDate = data;
} public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
} public Date getSystemDate() {
return systemDate;
} public void setSystemDate(Date systemDate) {
this.systemDate = systemDate;
}
}

  测试代码:

public class Test {

    public static void main(String[] args) throws JAXBException {
Children st = new Children("逝清雪", new Date());
String xml = XStreamUtil.marshal(st, Children.class);
System.out.println(StringUtils.formatXml(xml));
}
}

  测试结果:

<?xml version="1.0" encoding="gb2312"?>
<Children>
<USER_NAME>逝清雪</USER_NAME>
</Children>