簡介:
JAXB(Java Architecture for XML Binding) 是一個業界的標准,是一項可以根據XML Schema產生Java類的技術。該過程中,JAXB也提供了將XML實例文檔反向生成Java對象樹的方法,並能將Java對象樹的內容重新寫到XML實例文檔。從另一方面來講,JAXB提供了快速而簡便的方法將XML模式綁定到Java表示,從而使得Java開發者在Java應用程序中能方便地結合XML數據和處理函數。
一個簡單的例子:
Person.class類
package io.renren.modules.temptest; import lombok.Data; import javax.xml.bind.annotation.XmlRootElement; /** * @author xubo * @ClassNme Person * @Description TODO * @Date 2022-03-30 17:03:29 * @version1.0 */ @Data @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) {//這里沒有id this.name = name; this.gender = gender; this.addr = addr; this.area = area; } }
JAXBTest.class類:
package io.renren.modules.temptest; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import java.io.File; /** * @author xubo * @ClassNme JAXBTest * @Description TODO * @Date 2022-03-30 17:08:26 * @version1.0 */ public class JAXBTest { //對象轉xml public void generateXML() { Person person = new Person("abc", "男", "北京", "朝陽區"); File file = new File("D:\\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(); } } //xml轉對象 public void generateBean() { File file = new File("D:\\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(); } } }
Test.class類:
package io.renren.modules.temptest; /** * @author xubo * @ClassNme Test * @Description TODO * @Date 2022-03-30 17:52:48 * @version1.0 */ public class Test { public static void main(String[] args){ JAXBTest jaxbTest = new JAXBTest(); //對象轉xml jaxbTest.generateXML(); //xml轉對象 jaxbTest.generateBean(); } }
測試結果:
運行方法:generateXML():
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <person> <addr>北京</addr> <area>朝陽區</area> <gender>男</gender> <id>0</id> <name>abc</name> </person>
此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靜態調用相應的工具方法就行了,於是上面的測試方法可以寫的更簡練些:
下面為了測試方便,還是在JAXBTest類中寫了兩個靜態方法,運行后結果還是一樣的。代碼看起來更加簡潔,這只是簡單的功能。
package io.renren.modules.temptest; import javax.xml.bind.JAXB; import java.io.File; /** * @author xubo * @ClassNme Test * @Description TODO * @Date 2022-03-30 17:52:48 * @version1.0 */ public class Test { public static void main(String[] args){ // JAXBTest jaxbTest = new JAXBTest(); // //對象轉xml // jaxbTest.generateXML(); // // //xml轉對象 // jaxbTest.generateBean(); generateXML1(); generateBean1(); } //為了測試方便使用靜態 public static void generateXML1() { Person person = new Person("abc", "男", "北京", "朝陽區"); File file = new File("D:\\person1.xml"); JAXB.marshal(person, file); } public static void generateBean1() { File file = new File("D:\\person1.xml"); Person person = JAXB.unmarshal(file, Person.class); System.out.println(person); } }
結果顯示:上面修改了生成文件名名字person1.xml
運行generateXML1()方法

運行generateBean1()方法

直接使用默認的配置,已經足夠應付大多數情況,讀者可以試一下。
常用注解:
@XmlRootElement:
作用和用法:
類級別的注解,將類映射為xml全局元素,也就是根元素。就像spring配置文件中的beans。上面的例子中我將該注解用在了person類上,生成了<person>根元素。常與@XmlType,@XmlAccessorType,@XmlAccessorOrder連用。
屬性:
該注解含有name和namespace兩個屬性。namespace屬性用於指定生成的元素所屬的命名空間。name屬性用於指定生成元素的名字,若不指定則默認使用類名小寫作為元素名。修改上面的例子,在該注解上使用name,namespace屬性:
package io.renren.modules.temptest; import lombok.Data; import javax.xml.bind.annotation.XmlRootElement; /** * @author xubo * @ClassNme Person * @Description TODO * @Date 2022-03-30 17:03:29 * @version1.0 */ @Data @XmlRootElement(name = "xubo" , namespace = "www.baidu.com") 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) {//這里沒有id this.name = name; this.gender = gender; this.addr = addr; this.area = area; } }
重新運行方法generateXML1(),可以看到根元素變名字了,注意根元素跟之前不一樣了,多了ns2
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:xubo xmlns:ns2="www.baidu.com"> <addr>北京市</addr> <area>朝陽區潘家園</area> <gender>男</gender> <id>0</id> <name>abccc</name> </ns2:xubo>
插播一段,我是參考大佬文章然后手動運算了一遍,注意我的最上面的Person.class類里面跟原文章是有區別的,區別指出在沒有寫get set 方法,使用了@Data注解,自然也沒有添加在set方法上面添加@XmlElement注解,我是在學習注解的時候才發現我自己的Person類是沒有添加該注解的,但是運行后的xml文件顯示也是正常的,這點我暫時還不懂原因是什么。
原主的文章中的person是這樣子
@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 + '\'' + '}'; } }
為了學習的方便我們這里修改一下代碼,
@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,注意不能和@XmlAttribute用在同一個地方,否則不生效。下面會有驗證。
作用和用法:
字段和方法級別的注解。該注解會將字段或get/set方法對應的字段映射成本類對應元素的屬性,屬性名默認使用字段名或get/set方法去掉前綴剩下部分首字母小寫(在字段名和get/set方法符合命名規范的情況下)。修改上面例子:
@XmlAttribute public void setGender(String gender) { this.gender = gender; }
我實際操作,正確的應該和上面代碼一樣。不然好像@XmlAttribute不會生效

生成的xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <person> <addr></addr> <area>林州市</area> <gender>男</gender> <id>0</id> <name>abc</name> </person>
我修改后,把@XmlElement注解刪除掉,

生成的xml文件:gender本類對應元素的屬性,位置變了。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <person gender="男"> <addr></addr> <area>林州市</area> <id>0</id> <name>abc</name> </person>
對應的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:id元素消失了。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <person gender="男"> <addr></addr> <area>林州市</area> <name>abc</name> </person>
屬性:
該注解沒有屬性。
@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:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <human gender="男"> <addr></addr> <area>林州市</area> <key> <roomNum>002</roomNum> </key> <key> <roomNum>001</roomNum> </key> <name>abc</name> </human>
但同一元素應該需要被“包裝”一下才顯得有層次感,所以可以使用@XmlElementWrapper來實現
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <human gender="男"> <addr></addr> <area>林州市</area> <keys> <key> <roomNum>002</roomNum> </key> <key> <roomNum>001</roomNum> </key> </keys> <name>abc</name> </human>
這樣是不是好多了?
屬性:
該注解有name、nillable、namespace、required四個屬性,用法同上,不再贅述。
@XmlJavaTypeAdapter
作用和用法:
包、類、字段,方法、參數級別的注解。解決java日期(Date),數字(Number)格式化問題。直接看例子,修改Person類,添加一個Date類型字段:
Person類:
package io.renren.modules.temptest; import javax.xml.bind.annotation.*; import java.util.Date; import java.util.HashSet; import java.util.Set; @XmlRootElement(name = "human") @XmlAccessorType(XmlAccessType.PUBLIC_MEMBER) @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) public class Person { private int id; private String name; private String gender; private String addr; private String area; public Person() { } private Set<Key> key = new HashSet<>(); 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; } @XmlElementWrapper(name = "keys") @XmlElement public void setKey(Set<Key> key) { this.key = key; } public int getId() { return id; } @XmlTransient public void setId(int id) { this.id = id; } public String getName() { return name; } private Date date = new Date();//添加的日期類是在這里,應該寫上面的,主要是為了集中看起來方便一點 public Date getDate() { return date; } @XmlElement public void setDate(Date date) { this.date = date; } @XmlElement public void setName(String name) { this.name = name; } public String getGender() { return gender; } @XmlAttribute 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 + '\'' + '}'; } }
生成的xml文件:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <human gender="男"> <addr></addr> <area>林州市</area> <date>2022-04-02T15:30:14.175+08:00</date> <keys> <key> <roomNum>002</roomNum> </key> <key> <roomNum>001</roomNum> </key> </keys> <name>abc</name> </human>
這樣的date格式顯然令人不滿意,我們需要例如“2018-05-20”這樣的格式。這就需要@XmlJavaTypeAdapter注解,自定義一個適配器來解決這個問題。該注解的用法就是自定義適配器並繼承XmlAdapter類,實現里面的marshal和unmarshal方法,並在該注解上引用。修改例子:
自定義的DateAdapter:
package io.renren.modules.temptest; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.text.SimpleDateFormat; import java.util.Date; /** * @author xubo * @ClassNme DateAdapter * @Description TODO * @Date 2022-04-02 15:33:18 * @version1.0 */ public class DateAdapter extends XmlAdapter<String, Date> { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 1.格式化:Date -->String // String format(Date date) 將Date格式化為日期/時間字符串 // 2.解析:String -->Date // Date parse(String source) 將符合格式的指定字符串轉換為Date @Override public Date unmarshal(String v) throws Exception { return sdf.parse(v); } @Override public String marshal(Date v) throws Exception { return sdf.format(v); } }
Person類:
@XmlJavaTypeAdapter(DateAdapter.class) @XmlElement public void setDate(Date date) { this.date = date; }
生成的xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <human gender="男"> <addr></addr> <area>林州市</area> <date>2022-04-02</date> <keys> <key> <roomNum>002</roomNum> </key> <key> <roomNum>001</roomNum> </key> </keys> <name>abc</name> </human>
完美解決。
屬性:
常用的就是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。
再次強調,如果以上解釋看不懂,先學xsd。傳送門:《XSD學習》
