過往的項目中數據存儲都離不開數據庫,不過最近做的一個項目的某些數據(比如人員信息、菜單、權限等等)卻完全沒有涉及任何數據庫操作,直接XML搞定。這里無意比較優劣,因為數據庫存儲和XML存儲本就有不同的適用場景,盲目比較毫無意義,只是因為業務需要,僅此而已。先來概念一下——XML,可擴展標記語言,設計宗旨是用來傳輸數據而非顯示數據,其遵循W3C標准,是一種通用的數據交換格式,具有很強的跨平台性,並且數據無需轉換,所以,如果你要將數據做跨平台傳輸,那么把數據保存在 XML 文件中是有好處的。當然,這里要說明,由於XML僅僅是作為一種文檔模式的結構化存儲,所以並不適用於大數據量的存儲。現在的Java中有很多類庫比如DOM、SAX、JDOM和DOM4J等等都可以操作XML,但如果僅僅是想做JavaBean和XML節點元素的互相轉換,而不涉及動態XML的處理,那么JAXB絕對是一個不錯的選擇。在比較新的jdk版本中,JAXB都是jdk的擴展包javax中自帶的類庫,不需要你引入第三方jar包。下面,博主正式給看客上菜,詳細介紹一下JAXB的實際用法——
一 JavaBean和XML相互轉換初體驗

1 package model; 2 3 import javax.xml.bind.annotation.*; 4 import java.io.Serializable; 5 6 //JavaBean代碼 7 8 @XmlType(propOrder = {}) 9 @XmlRootElement(name = "user") 10 @XmlAccessorType(XmlAccessType.PROPERTY) 11 @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) 12 public class User implements Serializable { 13 14 private String userName; 15 private int age; 16 private String role; 17 private String bibi; 18 19 public User() { 20 } 21 22 public User(String userName, int age, String role, String bibi) { 23 this.userName = userName; 24 this.age = age; 25 this.role = role; 26 this.bibi = bibi; 27 } 28 29 public String getUserName() { 30 return userName; 31 } 32 33 public void setUserName(String userName) { 34 this.userName = userName; 35 } 36 37 @XmlAttribute 38 public int getAge() { 39 return age; 40 } 41 42 public void setAge(int age) { 43 this.age = age; 44 } 45 46 @XmlElement 47 public String getRole() { 48 return role; 49 } 50 51 public void setRole(String role) { 52 this.role = role; 53 } 54 55 @XmlTransient 56 public String getBibi() { 57 return bibi; 58 } 59 60 public void setBibi(String bibi) { 61 this.bibi = bibi; 62 } 63 64 @Override 65 public String toString() { 66 return "User{" + 67 "userName='" + userName + '\'' + 68 ", age=" + age + 69 ", role='" + role + '\'' + 70 ", bibi='" + bibi + '\'' + 71 '}'; 72 } 73 } 74 75 //測試 76 public class test { 77 @Test 78 public void saveXmlTest() { 79 User user = new User("陳本布衣", 2018, "超級管理員","瞎嗶嗶"); 80 File file = new File("E://user.xml"); 81 try { 82 JAXBContext jaxbContext = JAXBContext.newInstance(User.class); 83 Marshaller marshaller = jaxbContext.createMarshaller(); 84 //格式化輸出,即按標簽自動換行,否則就是一行輸出 85 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 86 //設置編碼(默認編碼就是utf-8) 87 marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); 88 //是否省略xml頭信息,默認不省略(false) 89 marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false); 90 marshaller.marshal(user, file); 91 } catch (JAXBException e) { 92 e.printStackTrace(); 93 } 94 } 95 96 @Test 97 public void getUserTest() { 98 File file = new File("E://user.xml"); 99 try { 100 JAXBContext jaxbContext = JAXBContext.newInstance(User.class); 101 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 102 User user = (User) unmarshaller.unmarshal(file); 103 System.out.println(user.toString()); 104 } catch (JAXBException e) { 105 e.printStackTrace(); 106 } 107 } 108 }
生成的XML:
二 JAXB使用基礎介紹
① 常用API
- JAXBContext類,是應用的入口,通過該類創建序列化和反序列化對象,也即編組對象和解組對象;
- Marshaller 編組接口,將Java對象序列化為XML數據;
- Unmarshaller 解組接口,將XML數據反序列化為Java對象。
② 常用注解
- @XmlRootElement,將Java類或枚舉映射成XML元素根節點,是唯一一個必須注解,name屬性指定根節點名稱,不指定默認為類名的小寫;
- @XmlElement,將Java類的一個屬性映射為XML節點元素,name屬性可自定義元素名;
- @XmlAttribute,將Java類的一個屬性映射為XML節點元素的屬性,name屬性可自定義屬性名;
- @XmlType,將Java類或枚舉類型映射到XML模式類型,常與@XmlRootElement、@XmlAccessorType共用,propOrder屬性定義字段生成的XML節點順序;
- @XmlAccessorType,控制字段或屬性的序列化。屬性XmlAccessType有4個常量值:FIELD表示JAXB將自動綁定Java類中的每個非靜態的(static)、非瞬態的(由@XmlTransient標注)字段到XML;PROPERTY表示java對象中所有通過getter/setter方式綁定成屬性到XML;PUBLIC_MEMBER表示Java對象中所有的public訪問權限的成員變量和通過getter/setter方式訪問的成員變量,該值為默認值;NONE表示Java對象的所有屬性都不映射為XML的元素;
- @XmlAccessorOrder,控制JAXB 綁定類中屬性和字段的排序,有兩個屬性,AccessorOrder.ALPHABETICAL——對生成的XML元素按字母書序排序,XmlAccessOrder.UNDEFINED——不排序,默認為該值;
- @XmlJavaTypeAdapter,自定義適配器(即擴展抽象類XmlAdapter並覆蓋marshal()和unmarshal()方法),解決日期(Date),數字(Number)格式化問題;
- @XmlElementWrapper ,對於數組或集合(即包含多個元素的成員變量),生成一個包裝該數組或集合的XML元素(稱為包裝器),該注解只能用在集合上;
- @XmlTransient ,用於標示在由Java對象映射XML時,忽略此屬性,在生成的XML文件中將不出現此元素。
③ 實際應用中注意的問題
① 如果JavaBean中定義了有參的構造器,那么必須同時定義無參構造器,否則轉XML會拋無默認構造函數的異常;
② 成員變量值為NULL時,將不會映射成對應的XML元素——由於基本數據類型默認值不為空,所以基本數據類型不設值也會映射成XML元素,值為默認值,所以如果模型需要基本數據,在屬性定義的時候盡量使用包裝類型;
③ @XmlAccessorType 注解中如果屬性值為XmlAccessType.FIELD,則表示通過成員變量來映射,set/get方法上的映射注解就是多余的,所以如果此時set/get方法上再標注元素或者屬性映射注解,將拋屬性重復性異常;屬性值為XmlAccessType.NONE不映射為XML元素的前提是Java字段或set/get方法上都沒有映射注解;
④ @XmlType propOrder屬性能夠自定義字段的排序,該屬性如果設置,要么寫成{}的形式,否則在就必須將所有@XmlElement標注或者沒有@XmlElement標注的但實際上會被映射為XML節點的字段添加到排序列表,不然會拋異常;如果propOrder屬性設置有值,@XmlAccessorOrder注解的元素排序規則將失效;
三 應用實際
先准備好測試用的工具方法:
1 package util; 2 3 4 import model.User; 5 6 import javax.xml.bind.JAXBContext; 7 import javax.xml.bind.JAXBException; 8 import javax.xml.bind.Marshaller; 9 import javax.xml.bind.Unmarshaller; 10 import java.io.File; 11 12 public class JaxbUtil { 13 14 public static void convertToXml(Object obj, File file) { 15 try { 16 JAXBContext jaxbContext = JAXBContext.newInstance(User.class); 17 Marshaller marshaller = jaxbContext.createMarshaller(); 18 //格式化輸出,即按標簽自動換行,否則就是一行輸出 19 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 20 //設置編碼(默認編碼就是utf-8) 21 marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); 22 //是否省略xml頭信息,默認不省略(false) 23 marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false); 24 marshaller.marshal(obj, file); 25 //控制台輸出 26 marshaller.marshal(obj,System.out); 27 } catch (JAXBException e) { 28 e.printStackTrace(); 29 } 30 } 31 32 public static <T> T convertToJavaBean(Class<T> clz, File file) { 33 try { 34 JAXBContext jaxbContext = JAXBContext.newInstance(clz); 35 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 36 T t = (T) unmarshaller.unmarshal(file); 37 return t; 38 } catch (JAXBException e) { 39 e.printStackTrace(); 40 } 41 return null; 42 } 43 }
① 簡單對象處理
簡單對象處理起來比較簡單,譬如人員對象User中包含菜單Menu,只需將定義的普通Menu對象也按照JAXB的注解進行標注,在User對象中當成普通字段一樣的定義即可——

@XmlType(propOrder = {"userName","role","menu"}) @XmlRootElement(name = "user") @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) public class User implements Serializable { private String userName; private int age; private String role; private String bibi; private Menu menu; public User() { } public User(String userName, int age, String role, String bibi) { this.userName = userName; this.role = role; this.age = age; this.bibi = bibi; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @XmlAttribute public int getAge() { return age; } public void setAge(int age) { this.age = age; } @XmlElement(nillable=true) public String getRole() { return role; } public void setRole(String role) { this.role = role; } @XmlTransient public String getBibi() { return bibi; } public void setBibi(String bibi) { this.bibi = bibi; } @XmlElement public Menu getMenu() { return menu; } public void setMenu(Menu menu) { this.menu = menu; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", age=" + age + ", role='" + role + '\'' + ", menu=" + menu + '}'; } } //菜單對象 @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Menu { private String name; private String id; public Menu() { } public Menu(String name, String id) { this.name = name; this.id = id; } @Override public String toString() { return "Menu{" + "name='" + name + '\'' + ", id='" + id + '\'' + '}'; } }
② 集合處理
實際應用場景中集合應用要更常見一些,比如上面的用戶菜單,一個用戶肯定會有多個不同的菜單,所以,我們來將上面的菜單改用集合處理——

1 package model; 2 3 import javax.xml.bind.annotation.*; 4 import java.io.Serializable; 5 import java.util.Date; 6 import java.util.List; 7 8 @XmlType(propOrder = {"userName", "role", "menus"}) 9 @XmlRootElement(name = "user") 10 @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) 11 public class User implements Serializable { 12 13 private String userName; 14 private int age; 15 private String role; 16 private String bibi; 17 private List<Menu> menus; 18 19 public User() { 20 } 21 22 public User(String userName, int age, String role, String bibi) { 23 this.userName = userName; 24 this.role = role; 25 this.age = age; 26 this.bibi = bibi; 27 } 28 29 public String getUserName() { 30 return userName; 31 } 32 33 public void setUserName(String userName) { 34 this.userName = userName; 35 } 36 37 @XmlAttribute 38 public int getAge() { 39 return age; 40 } 41 42 public void setAge(int age) { 43 this.age = age; 44 } 45 46 @XmlElement 47 public String getRole() { 48 return role; 49 } 50 51 public void setRole(String role) { 52 this.role = role; 53 } 54 55 @XmlTransient 56 public String getBibi() { 57 return bibi; 58 } 59 60 public void setBibi(String bibi) { 61 this.bibi = bibi; 62 } 63 64 @XmlElement 65 public List<Menu> getMenus() { 66 return menus; 67 } 68 69 public void setMenus(List<Menu> menus) { 70 this.menus = menus; 71 } 72 73 @Override 74 public String toString() { 75 return "User{" + 76 "userName='" + userName + '\'' + 77 ", age=" + age + 78 ", role='" + role + '\'' + 79 ", menus=" + menus + 80 '}'; 81 } 82 }

1 package model; 2 3 import javax.xml.bind.annotation.XmlAttribute; 4 import javax.xml.bind.annotation.XmlRootElement; 5 import java.util.List; 6 7 @XmlRootElement 8 public class Menu { 9 private String name; 10 private String id; 11 private List<Menu> child; 12 13 public Menu() { 14 } 15 16 public Menu(String name, String id) { 17 this.name = name; 18 this.id = id; 19 } 20 @XmlAttribute 21 public String getName() { 22 return name; 23 } 24 25 public void setName(String name) { 26 this.name = name; 27 } 28 @XmlAttribute 29 public String getId() { 30 return id; 31 } 32 33 public void setId(String id) { 34 this.id = id; 35 } 36 37 public List<Menu> getChild() { 38 return child; 39 } 40 41 public void setChild(List<Menu> child) { 42 this.child = child; 43 } 44 45 @Override 46 public String toString() { 47 return "Menu{" + 48 "name='" + name + '\'' + 49 ", id='" + id + '\'' + 50 '}'; 51 } 52 }

1 package test; 2 3 4 import model.Menu; 5 import model.User; 6 import org.junit.Test; 7 import util.JaxbUtil; 8 import java.io.File; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 public class test { 13 @Test 14 public void saveXmlTest() { 15 User user = new User("陳本布衣", 2018, "超級管理員","瞎嗶嗶"); 16 List<Menu> list1 = new ArrayList<>(); 17 Menu menu1 = new Menu("系統管理","9527"); 18 Menu child1 = new Menu("權限管理","9999"); 19 Menu child2 = new Menu("用戶管理","2322"); 20 list1.add(child1); 21 list1.add(child2); 22 menu1.setChild(list1); 23 List<Menu> list2 = new ArrayList<>(); 24 Menu menu2 = new Menu("參數配置","2222"); 25 Menu child3 = new Menu("權限管理","3333"); 26 Menu child4 = new Menu("用戶管理","4444"); 27 list2.add(child3); 28 list2.add(child4); 29 menu2.setChild(list2); 30 List<Menu> menus = new ArrayList<>(); 31 menus.add(menu1); 32 menus.add(menu2); 33 user.setMenus(menus); 34 File file = new File("E://user.xml"); 35 JaxbUtil.convertToXml(user,file); 36 } 37 38 @Test 39 public void getUserTest() { 40 File file = new File("E://user.xml"); 41 User user = JaxbUtil.convertToJavaBean(User.class, file); 42 System.out.println(user); 43 } 44 }
上面的菜單中似乎少了點層次關系,這個時候可以使用集合包裝器注解@XmlElementWrapper自定義一個包裝節點,這樣產生的XML文檔才更有層次:
1 @XmlElementWrapper(name = "menu") 2 @XmlElement 3 public List<Menu> getMenus() { 4 return menus; 5 }
最終產生的XML文檔就是這樣的:
③ 格式化處理
業務數據中日期、數值通常是必不可少的,在數據存儲的時候,這些數據通常都需要做格式化處理,比如將日期格式化,貨幣型數值處理等等。JAXB中格式化處理需要繼承適配器抽象類XmlAdapter,並覆寫其序列化和反序列化的方法,這里僅用常用的日期格式化為例:
1 package adapter; 2 3 import javax.xml.bind.annotation.adapters.XmlAdapter; 4 import java.text.DateFormat; 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 8 public class DateAdapter extends XmlAdapter<String, Date> { 9 private static final DateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 10 11 @Override 12 public Date unmarshal(String date) throws Exception { 13 return SDF.parse(date); 14 } 15 16 @Override 17 public String marshal(Date date) throws Exception { 18 return SDF.format(date); 19 } 20 }
將該適配器通過注解應用到User類表時間的date字段上:
1 @XmlJavaTypeAdapter(DateAdapter.class) 2 public Date getDate() { 3 return date; 4 } 5 6 public void setDate(Date date) { 7 this.date = date; 8 }
最后的時間就是按照格式化輸出——
④ 數據的修改
由於XML是文檔數據類型,對於文檔數據的修改操作,通常采用的都是先將文本內容全部讀取到內存,修改完成后再寫回去文本的方式——雖然Java中有RandomAccessFile類可以實現對文本任意位置的訪問修改,但博主以為,在JAXB這種對象模型映射成XML的業務中並不適用。我們將上面的模型稍微簡化一下,完成根據用戶id修改用戶數據的測試——

@XmlType(propOrder = {"users", "userName", "role", "remark"}) @XmlRootElement(name = "user") @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) public class User implements Serializable { private String userName; private Integer id; private String role; private String remark; private List<User> users; public User() { } public User(String userName, Integer id, String role, String remark) { this.userName = userName; this.id = id; this.role = role; this.remark = remark; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @XmlAttribute public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @XmlElement public String getRole() { return role; } public void setRole(String role) { this.role = role; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } } //測試代碼 public class test { @Test public void editUser() { User root = new User(); File file = new File("E://user.xml"); //模擬修改后的一條數據 User editUser = new User("陳本布衣", 2018, "超級管理員","數據修改后"); User user = JaxbUtil.convertToJavaBean(User.class, file); List<User> users = user.getUsers(); for (int i = 0; i < users.size(); i++) { if(users.get(i).getId().equals(editUser.getId())){ users.set(i,editUser); } } root.setUsers(users); JaxbUtil.convertToXml(root,file); } }
最后,在XML文檔中你可以看到文檔內容已經被修改了——
四 問題補充
上述博文中描述的工具方法僅僅是出於學習中追根問本的目的寫得稍微冗余了些,實際上,我所知道的是最遲從jdk1.7開始,JAXB就對解組和編組的方法進行了更簡單的封裝,所以,實際項目中除非自己要進行個性化設置,否則大可不用自己再創建JAXBContext實例,直接通過JAXB靜態調用相應的工具方法就行了,有興趣的看官稍微跟蹤一下源碼就能了然,於是上面的工具方法可以寫得更簡單——
1 package util; 2 3 4 import javax.xml.bind.JAXB; 5 import java.io.File; 6 7 public class JaxbUtil { 8 9 public static void convertToXml(Object obj, File file) { 10 JAXB.marshal(obj,file); 11 } 12 13 public static <T> T convertToJavaBean(Class<T> clz, File file) { 14 return JAXB.unmarshal(file, clz); 15 } 16 }
OK,對於JAXB的知識分享就差不多這么些了。對於這種比較單一技能點的學習,就是根據API多寫點代碼練習測試,從測試的結果對錯中總結出自己的深層理解,並在實際項目學以致用,不變應萬變,望看官讀畢都有所收獲!