JAXB注解的使用详解

前言:

  最近一直在做各种接口的对接,接触最多的数据类型就是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;
   }

}
View Code

  工具类代码:

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);
    }
    
}
View Code

  测试代码:

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));
    }
}
View Code

一、@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;

   } //省略下面代码
View Code

  测试结果:

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


Process finished with exit code 0
View Code

二、@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.
     */
View Code

      补充:

        (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.
     */
View Code

      补充:

        (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;
}
View Code

      理解:下面则是对此的描述翻译大概是《每个公共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.
     */
View Code

      补充:

        (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.
*/
View Code

      补充:

        任何字段,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;

   }//后面省略
View Code

  测试结果:

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

四、@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;
   }
View Code

  测试结果:

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

五、@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;

   }//后面代码省略……
View Code

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

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

  测试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;

   }//后面代码省略……
View Code

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

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

六、@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;
    }
}
View Code
@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;
   }
}
View Code

  测试代码:

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));
    }
}
View Code

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

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

  有@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>
View Code

七、@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);
    }

}
View Code

  测试代码:

@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;
    }
}
View Code

  测试代码:

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));
    }
}
View Code

  测试结果:

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

 八、@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;
    }
}
View Code

  测试代码:

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));
    }
}
View Code

  测试结果:

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

 

posted @ 2019-04-04 14:33  は問わない  阅读(1527)  评论(0编辑  收藏  举报