1. 程式人生 > >JAXB常用註解講解

JAXB常用註解講解

簡介:

JAXB(Java Architecture for XML Binding) 是一個業界的標準,是一項可以根據XML Schema產生Java類的技術。該過程中,JAXB也提供了將XML例項文件反向生成Java物件樹的方法,並能將Java物件樹的內容重新寫到XML例項文件。從另一方面來講,JAXB提供了快速而簡便的方法將XML模式繫結到Java表示,從而使得Java開發者在Java應用程式中能方便地結合XML資料和處理函式。

一個簡單的例子:

Person.class :

@XmlRootElement
public class Person {
    private int id;
    private String name;
    private String gender;
    private String addr;
    private String area;
 
    public Person() {
    }
 
    public Person(String name, String gender, String addr, String area) {
        this.name = name;
        this.gender = gender;
        this.addr = addr;
        this.area = area;
    }
 
    public int getId() {
        return id;
    }
 
    @XmlElement
    public void setId(int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    @XmlElement
    public void setName(String name) {
        this.name = name;
    }
 
    public String getGender() {
        return gender;
    }
 
    @XmlElement
    public void setGender(String gender) {
        this.gender = gender;
    }
 
    public String getAddr() {
        return addr;
    }
 
    @XmlElement
    public void setAddr(String addr) {
        this.addr = addr;
    }
 
    public String getArea() {
        return area;
    }
 
    @XmlElement
    public void setArea(String area) {
        this.area = area;
    }
 
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", addr='" + addr + '\'' +
                ", area='" + area + '\'' +
                '}';
    }
}
Test:

public class JAXBTest {
    @Test
    public void generateXML() {
        Person person = new Person("abc", "男", "北京", "朝陽區");
 
        File file = new File("E:\\person.xml");
        JAXBContext jc = null;
        try {
            //根據Person類生成上下文物件
            jc = JAXBContext.newInstance(Person.class);
            //從上下文中獲取Marshaller物件,用作將bean編組(轉換)為xml
            Marshaller ma = jc.createMarshaller();
            //以下是為生成xml做的一些配置
            //格式化輸出,即按標籤自動換行,否則就是一行輸出
            ma.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            //設定編碼(預設編碼就是utf-8)
            ma.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
            //是否省略xml頭資訊,預設不省略(false)
            ma.setProperty(Marshaller.JAXB_FRAGMENT, false);
 
            //編組
            ma.marshal(person, file);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
 
    @Test
    public void generateBean() {
        File file = new File("E:\\person.xml");
        JAXBContext jc = null;
        try {
            jc = JAXBContext.newInstance(Person.class);
            Unmarshaller uma = jc.createUnmarshaller();
            Person person = (Person) uma.unmarshal(file);
            System.out.println(person);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}
測試結果:

generateXML():

此xml相當於該xsd檔案:

<xs:element name="person">
    <xs:complexType>
      <xs:sequence>
        <xs:element type="xs:string" name="addr" minOccurs="0"/>
        <xs:element type="xs:string" name="area" minOccurs="0"/>
        <xs:element type="xs:string" name="gender" minOccurs="0"/>
        <xs:element type="xs:int" name="id" minOccurs="0"/>
        <xs:element type="xs:string" name="name" minOccurs="0"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

generateBean():

是不是很方便?這只是一個最簡單的小例子,下文會在這個例子的基礎上介紹和講解其他的一些常用JAXB註解。因為會使用到部分xsd的知識,不瞭解的讀者可以看我另一篇部落格:《xsd學習:超詳細解析》,否則下文會有些理解困難。

注:從jdk1.7開始,JAXB就對解組和編組的方法進行了更簡單的封裝,所以實際專案中除非自己要進行個性化設定,否則大可不用自己再建立JAXBContext例項,直接通過JAXB靜態呼叫相應的工具方法就行了,於是上面的測試方法可以寫的更簡練些:

public class JAXBTest {
    @Test
    public void generateXML() {
        Person person = new Person("abc", "男", "北京", "朝陽區");
        File file = new File("E:\\person.xml");
        JAXB.marshal(person, file);
    }
 
    @Test
    public void generateBean() {
        File file = new File("E:\\person.xml");
        Person person = JAXB.unmarshal(file, Person.class);
 
        System.out.println(person);
    }
}


直接使用預設的配置,已經足夠應付大多數情況,讀者可以試一下。

常用註解:

@XmlRootElement:

作用和用法:

類級別的註解,將類對映為xml全域性元素,也就是根元素。就像spring配置檔案中的beans。上面的例子中我將該註解用在了person類上,生成了<person>根元素。常與@XmlType,@XmlAccessorType,@XmlAccessorOrder連用。

屬性:

該註解含有name和namespace兩個屬性。namespace屬性用於指定生成的元素所屬的名稱空間。name屬性用於指定生成元素的名字,若不指定則預設使用類名小寫作為元素名。修改上面的例子,在該註解上使用name屬性:

@XmlRootElement(name = "human")
public class Person {
    private int id;
    private String name;
    ...
    ...
}    //省略下面程式碼
生成的xml:

@XmlElement

作用和用法:

欄位,方法,引數級別的註解。該註解可以將被註解的欄位(非靜態),或者被註解的get/set方法對應的欄位對映為本地元素,也就是子元素。預設使用欄位名或get/set方法去掉字首剩下部分小寫作為元素名(在欄位名和get/set方法符合命名規範的情況下)。上面例子中,id、addr、name、gender、area都被對映成了<person>元素的子元素。下文會配合@XmlAccessorType註解詳細講解該註解的用法。常與@XmlValue,@XmlJavaTypeAdapter,@XmlElementWrapper連用。

屬性:

該註解的屬性常用的屬性有有:name、nillable、required、namespace、defaultValue

* name屬性可以指定生成元素的名字,同@XmlRootElement註解的name屬性一樣,不再舉例。

* nillable屬性可以指定元素的文字值是否可以為空,預設為false。修改上面例子:

@XmlElement(nillable = true)
    public void setName(String name) {
    this.name = name;
}
則生成的xsd(為了節省篇幅,只擷取必要的片段)為:

<xs:element name="name" type="xs:string" nillable="true" minOccurs="0"/>
* required屬性可以指定該元素是否必須出現,預設為false,所以在xsd中會有對應的屬性minOccurs="0"。修改該屬性為true

@XmlElement(nillable = true, required = true)
    public void setName(String name) {
    this.name = name;
}
生成的xsd檔案為:

<xs:element name="name" type="xs:string" nillable="true" minOccurs="1"/>
* namespace屬性可以指定該元素所屬的名稱空間

* defaultValue屬性可以指定該元素預設的文字值

@XmlAttribute

作用和用法:

欄位和方法級別的註解。該註解會將欄位或get/set方法對應的欄位對映成本類對應元素的屬性,屬性名預設使用欄位名或get/set方法去掉字首剩下部分首字母小寫(在欄位名和get/set方法符合命名規範的情況下)。修改上面例子:

@XmlAttribute
public void setGender(String gender) {
    this.gender = gender;
}
生成的xml:

對應的xsd:

<xs:element name="human">
    <xs:complexType>
      <xs:sequence>
        <xs:element type="xs:string" name="addr"/>
        <xs:element type="xs:string" name="area"/>
        <xs:element type="xs:byte" name="id"/>
        <xs:element type="xs:string" name="name"/>
      </xs:sequence>
      <xs:attribute type="xs:string" name="gender"/>
    </xs:complexType>
  </xs:element>
屬性:

該註解有name,required,namespace三個屬性。用法和@XmlElement註解相同,不再舉例,可以自己嘗試下。

@XmlTransient

作用和用法:

類,欄位,方法級別的註解。可使JAXB在對映xml元素時忽略被註解的類,欄位,get/set對應欄位。需要注意的是該註解與所有其他JAXB註釋相互排斥,也就是說與其他註釋連用就會報錯。修改上面例子:

@XmlTransient
public void setId(int id) {
    this.id = id;
}
生成的xml:

屬性:

該註解沒有屬性。

@XmlAccessorType

作用和用法:

包和類級別的註解。javaEE的API對該註解的解釋是:控制欄位是否被預設序列化。通俗來講,就是決定哪些欄位或哪些get/set方法對應的欄位會被對映為xml元素,需要注意的是欄位或get/set方法的訪問許可權(public/private)會影響欄位是否被對映為xml元素,下面會詳細講解。

屬性:

該註解只有一個value屬性,可取的值是一個名為XmlAccessType的列舉型別裡的值,下面詳細看一下這幾個值分別有什麼用:

XmlAccessType.PROPERTY:

官方解釋:

Every getter/setter pair in a JAXB-bound class will be automatically bound to XML, unless annotated by {@link XmlTransient}.
jaxb繫結類中的每個getter/setter對都將自動繫結到XML,除非用@XmlTransient註釋。
Fields are bound to XML only when they are explicitly annotated by some of the JAXB annotations.
只有在某些JAXB註釋顯式地註釋欄位時,欄位才被繫結到XML。
補充:

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元素。

XmlAccessType.FIELD:

官方解釋:

Every non static, non transient field in a JAXB-bound class will be automatically bound to XML, unless annotated by {@link XmlTransient}.
jaxb繫結類中的每個非靜態、非瞬態欄位都將自動繫結到XML,除非使用@XmlTransient進行註釋。
Getter/setter pairs are bound to XML only when they are explicitly annotated by some of the JAXB annotations.
只有當某些JAXB註釋顯式地對getter/setter對進行註釋時,它們才會繫結到XML。
補充:

1.每個非靜態的欄位(無論訪問許可權如何)都會被jaxb對映為xml元素,即使沒有get/set方法對,即使沒有使用@XmlElement元素,但最好加上該註解以表明該欄位要被對映為xml元素。

2.雖然沒有get/set方法對,也會發生對映,但加上get/set方法對也不會報錯,因為我們經常會使用這兩個方法。但注意,不能再在這兩個方法上使用@XmlElement方法,否則會報屬性重複的錯誤。

3.若在欄位上使用了@XmlTransient註解,但還想讓該欄位發生對映,需要在該欄位對應的get/set方法上新增@XmlElement

XmlAccessType.PUBLIC_MEMBER (該值為預設值):

官方解釋:

Every public getter/setter pair and every public field will be automatically bound to XML, unless annotated by {@link XmlTransient}.
每個公共getter/setter對和每個公共欄位都將自動繫結到XML,除非使用@XmlTransient註釋。
Fields or getter/setter pairs that are private, protected, or defaulted to package-only access are bound to XML only when they areexplicitly annotated by the appropriate JAXB annotations.
只有在適當的JAXB註釋顯式地註釋了欄位或getter/setter對之後,才會將它們繫結到XML。
補充:

1.每個訪問許可權為public的欄位,或者每個訪問許可權為public的get/set方法對,都會將欄位對映為xml元素,即使不使用@XmlElement,但最好加上。不可同時存在public欄位和對應的get/set方法對,不然會報屬性重複的錯誤。

2.若使用@XmlElement註解,需要注意只能在欄位或get/set方法新增,兩者任選其一,否則會報屬性重複的錯誤。

3.若欄位不為public,get/set方法為public並使用了@XmlTransient,需要在欄位上新增@XmlElement才會發生對映。

若欄位為public並使用了@XmlTransient,get/set方法對不為public,需要在get/set方法上使用@XmlElement才會對映。

XmlAccessType.NONE:

官方解釋:

None of the fields or properties is bound to XML unless they are specifically  annotated with some of the JAXB annotations.
任何欄位或屬性都不會繫結到XML,除非使用某些JAXB註釋對它們進行特別註釋。
補充:

任何欄位,get/set方法對都不會發生對映,除非使用某些註解,如@XmlElement,@XmlElementWrapper等。

@XmlAccessorOrder

作用和用法:

包和類級別的註解。控制生成元素的順序。

屬性:

只有一個value屬性,可取的值是一個名為XmlAccessOrder的列舉型別的兩個值,XmlAccessOrder.ALPHABETICAL 和 XmlAccessOrder.UNDEFINED。預設為XmlAccessOrder.UNDEFINED,代表按照類中欄位的順序生成元素的順序。

另一個值則代表按照字母表的順序對生成的元素排序。但奇怪的是,只有jaxb按照field生成元素時,預設值才會生效,否則總是按照字母表的順序排序。

@XmlElementWrapper

作用和用法:

欄位和方法級別的註解。圍繞被對映的xml元素生成包裝元素。主要用在集合物件對映後生成包裝對映結果的xml元素。 

修改上面的例子,新增一個Key類,在Person類中新增一個Key類的Set集合,修改如下:

Key類:

@XmlRootElement
public class Key {
    private String roomNum;
 
    public Key() {
    }
 
    public Key(String roomNum) {
        this.roomNum = roomNum;
    }
 
    public String getRoomNum() {
        return roomNum;
    }
 
    @XmlElement
    public void setRoomNum(String roomNum) {
        this.roomNum = roomNum;
    }
 
    @Override
    public String toString() {
        return "Key{" +
                "roomNum='" + roomNum + '\'' +
                '}';
    }
}
Person類:

@XmlRootElement(name = "human")
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public class Person {
    ...
    private Set<Key> key = new HashSet<>();
 
    public Person() {
    }
 
    public Person(String name, String gender, String addr, String area) {
        this.name = name;
        this.gender = gender;
        this.addr = addr;
        this.area = area;
        key.add(new Key("001"));    //向集合中新增兩個Key物件
        key.add(new Key("002"));
    }
    ...
 
    public Set<Key> getKey() {
        return key;
    }
 
    @XmlElement
    public void setKey(Set<Key> key) {
        this.key = key;
    }
}
生成的xml:

但同一元素應該需要被“包裝”一下才顯得有層次感,所以可以使用@XmlElementWrapper來實現:

@XmlElementWrapper(name = "keys")
@XmlElement
public void setKey(Set<Key> key) {
    this.key = key;
}
生成的xml:

這樣是不是好多了?

屬性:

該註解有name、nillable、namespace、required四個屬性,用法同上,不再贅述。

@XmlJavaTypeAdapter

作用和用法:

包、類、欄位,方法、引數級別的註解。解決java日期(Date),數字(Number)格式化問題。直接看例子,修改Person類,新增一個Date型別欄位:

Person類:

@XmlRootElement(name = "human")
/*@XmlType(propOrder = {
        "name",
        "gender",
        "addr",
        "area"
})*/
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public class Person {
    ...
    private Date date = new Date();
 
    ...
 
    public Date getDate() {
        return date;
    }
 
    @XmlElement
    public void setDate(Date date) {
        this.date = date;
    }
}
生成的xml:


這樣的date格式顯然令人不滿意,我們需要例如“2018-05-20”這樣的格式。這就需要@XmlJavaTypeAdapter註解,自定義一個介面卡來解決這個問題。該註解的用法就是自定義介面卡並繼承XmlAdapter類,實現裡面的marshal和unmarshal方法,並在該註解上引用。修改例子:

自定義的DateAdapter:

public class DateAdapter extends XmlAdapter<String, Date> {
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
 
    @Override
    public Date unmarshal(String date) throws Exception {
        return SDF.parse(date);
    }
 
    @Override
    public String marshal(Date date) throws Exception {
        return SDF.format(date);
    }
}
Person類:

@XmlJavaTypeAdapter(DateAdapter.class)
@XmlElement
public void setDate(Date date) {
    this.date = date;
}
生成的xml:

完美解決。

屬性:

常用的就是value屬性,其他屬性請自行研究。

@XmlValue:

作用和用法:

欄位和方法級別的註解。該註解的作用,簡單理解就是定義xml元素文字值的型別,例如在一個類的String型別欄位上使用該註解,則生成的元素文字值型別就是xsd:string,也就是定義一個xsd中的simpleType.若類中還有一個欄位並使用了@XmlAttribute註解,則是定義一個xsd中的complexType。

屬性:

@XmlType:

作用和用法:

類級別的註解。該註解有些複雜,主要使用的是它的propOrder屬性,簡單來說是用來定義xsd中的simpleType或complexType,從生成的xml中來看,它的作用就是指定生成元素的順序,具體看下圖:

簡單解釋下每行什麼意思:

* 若指定該註解的propOrder為{},會生成ComplexType並且使用xs:all指示器,表示所有被對映的元素都必須出現在xml中

* 若propOrder的值為{"name", "addr", "area"}(大括號中都是Person類的欄位名稱),會生成ComplexType並使用xs:sequence指示器,表示生成的xml元素必須按照propOrder指定的順序出現,也就間接實現了排序。

* 若不指定propOrder屬性(這與指定propOrder但值為{}不同),沒有欄位,會生成ComplexType幷包含一個空的xs:sequence指示器。

* 若不指定propOrder屬性,但含有被@XmlValue註解的欄位和被@XmlAttribute註解的欄位,會生成一個含有simpleContent的ComplexType。

* 若不指定propOrder屬性,但含有被@XmlValue註解的欄位而沒有被@XmlAttribute註解的欄位,會生成一個含有simpleType的ComplexType。