過往的項目中數據存儲都離不開數據庫,不過最近作的一個項目的某些數據(好比人員信息、菜單、權限等等)卻徹底沒有涉及任何數據庫操做,直接XML搞定。這裏無心比較優劣,由於數據庫存儲和XML存儲本就有不一樣的適用場景,盲目比較毫無心義,只是由於業務須要,僅此而已。先來概念一下——XML,可擴展標記語言,設計宗旨是用來傳輸數據而非顯示數據,其遵循W3C標準,是一種通用的數據交換格式,具備很強的跨平臺性,而且數據無需轉換,因此,若是你要將數據作跨平臺傳輸,那麼把數據保存在 XML 文件中是有好處的。固然,這裏要說明,因爲XML僅僅是做爲一種文檔模式的結構化存儲,因此並不適用於大數據量的存儲。如今的Java中有不少類庫好比DOM、SAX、JDOM和DOM4J等等均可以操做XML,但若是僅僅是想作JavaBean和XML節點元素的互相轉換,而不涉及動態XML的處理,那麼JAXB絕對是一個不錯的選擇。在比較新的jdk版本中,JAXB都是jdk的擴展包javax中自帶的類庫,不須要你引入第三方jar包。下面,博主正式給看客上菜,詳細介紹一下JAXB的實際用法——java
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:數據庫
① 若是JavaBean中定義了有參的構造器,那麼必須同時定義無參構造器,不然轉XML會拋無默認構造函數的異常;數組
② 成員變量值爲NULL時,將不會映射成對應的XML元素——因爲基本數據類型默認值不爲空,因此基本數據類型不設值也會映射成XML元素,值爲默認值,因此若是模型須要基本數據,在屬性定義的時候儘可能使用包裝類型;
app
③ @XmlAccessorType 註解中若是屬性值爲XmlAccessType.FIELD,則表示經過成員變量來映射,set/get方法上的映射註解就是多餘的,因此若是此時set/get方法上再標註元素或者屬性映射註解,將拋屬性重複性異常;屬性值爲XmlAccessType.NONE不映射爲XML元素的前提是Java字段或set/get方法上都沒有映射註解;
dom
④ @XmlType propOrder屬性可以自定義字段的排序,該屬性若是設置,要麼寫成{}的形式,不然在就必須將全部@XmlElement標註或者沒有@XmlElement標註的但實際上會被映射爲XML節點的字段添加到排序列表,否則會拋異常;若是propOrder屬性設置有值,@XmlAccessorOrder註解的元素排序規則將失效;ide
先準備好測試用的工具方法:函數
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多寫點代碼練習測試,從測試的結果對錯中總結出本身的深層理解,並在實際項目學以至用,不變應萬變,望看官讀畢都有所收穫!