由於目前在工作中一直用的dom4j+反射實現bean與xml的相互轉換,記錄一下,如果有不正確的地方歡迎大家指正~~~
一、反射機制
在此工具類中使用到了反射技術,所以提前也看了一些知識點,例如:http://www.51gjie.com/java/82(這里面有大量的例子可以學習)
二、dom4j
dom4j會將整個xml加載至內存,並解析成一個document對象,但是可能會造成內存溢出現象。
Document:表示整個xml文檔。文檔Document對象是通常被稱為DOM樹。
Element:表示一個xml元素。Element對象有方法來操作其子元素,它的文本,屬性和名稱空間
Attribute:表示元素的屬性。屬性有方法來獲取和設置屬性的值。它有父節點和屬性類型。
Node:代表元素,屬性或者處理指令。
三、dom4j讀取xml
讀取xml文檔主要依賴於org.dom4j.io包,翻看其中源碼可以看出提供DOMReader、SaxReader、XPPReader、XPP3Reader,我這里主要查看了SaxReader源碼,其他的沒有深入看過,所以使用SaxReader。
/** * 將xml字符串轉換為Document對象 * @param xml * @return */ public Document getDocumentByString(String xml) { //1.字符串輸入流 StringReader stringReader = new StringReader(xml); //2.獲取解析器 SAXReader saxReader = new SAXReader(); //3.聲明document對象 Document document = null; try { //4.解析xml,生成document對象 document = saxReader.read(stringReader); } catch (DocumentException e) { log.error("xml解析失敗",e); } return document; }
四、bean與xml的互轉方法
我這里測試案例,查詢學生學校信息,返回學校及多個學生信息。如下准備:
1.准備bean
請求實體bean:ReqSchool.java
public class ReqSchool { //學校編號 private String number; //學校名稱 private String name; //學校省份 private String province; //學校地址 private String address; //學生班級 private String stuclass; //學生姓名 private String stuname; //學生分數 private String stuscore; //省略set和get方法 }
響應實體bean:RspSchool.java
public class RspSchool { //學校編號 private String number; //學校名稱 private String name; //學校省份 private String province; //學校地址 private String address; //多個學生 private List<Student> students; //模擬測試數據,返回多個學生 //省略get和set方法 }
響應實體bean的泛型:RspStudent.java
public class RspStudent { //學生班級 private String stuclass; //學生姓名 private String stuname; //學生分數 private String stuscore; //省略set和get方法 }
2.准備xml
①請求模版requestXML
這里以${元素名}作為請求模版,也可以修改工具類進行改造。
<?xml version="1.0" encoding = "GBK"?> <SCHOOL> <Head> <number>${number}</number> <name>${name}</name> <province>${province}</province> <address>${address}</address> </Head> <Body> <stuclass>${stuclass}</stuclass> <stuname>${stuname}</stuname> <stuscore>${stuscore}</stuscore> </Body> </SCHOOL>
②響應responseXML
<?xml version="1.0" encoding = "GBK"?> <SCHOOL> <Head> <number>0001</number> <name>xxx實驗小學</name> <province>北京市</province> <address>西城區</address> </Head> <Body> <students> <student> <stuclass>高三二班</stuclass> <stuname>李四</stuname> <stuscore>100</stuscore> </student> <student> <stuclass>高三三班</stuclass> <stuname>張三</stuname> <stuscore>95</stuscore> </student> <student> <stuclass>高三四班</stuclass> <stuname>王五</stuname> <stuscore>0</stuscore> </student> </students> </Body> </SCHOOL>
3.工具類(可直接復制粘貼使用)
復制粘貼使用時,需保證和我這里的請求報文模版相同(即使用${元素名}),當然也可手動改造此方法。
@Slf4j public class XmlUtil { //${abc}正則 public static String varRegex = "\\$\\{\\s*(\\w+)\\s*(([\\+\\-])\\s*(\\d+)\\s*)?\\}"; /** * xml解析成document對象 * * @param xml * @return */ public Document getDocument(String xml) { StringReader stringReader = new StringReader(xml); SAXReader saxReader = new SAXReader(); Document document = null; try { document = saxReader.read(stringReader); } catch (DocumentException e) { } return document; } /** * xml與bean的相互轉換 * * @param element * @param direction 1:java2xml,2:xml2java * @param obj */ public void parseXml(Element element, String direction, Object obj) { //獲取當前元素的所有子節點(在此我傳入根元素) List<Element> elements = element.elements(); //判斷是否有子節點 if (elements != null && elements.size() > 0) { //進入if說明有子節點 //遍歷 for (Element e : elements) { //判斷轉換方向(1:java2xml;2:xml2java) if ("2".equals(direction)) //這里是xml轉bean { //聲明Field Field field = null; try { //反射獲取屬性 field = obj.getClass().getDeclaredField(e.getName()); } catch (Exception e1) { } //獲取當前屬性是否為list if (field!=null&&List.class.getName().equals(field.getType().getName())) { //反射獲取set方法 Method method = this.getDeclaredMethod(obj, "set".concat(this.toUpperCaseFirstOne(e.getName())), new Class[]{List.class}); //聲明臨時list List temList = new ArrayList(); if (method!=null) { try { //反射調用obj的當前方法,可變參數為templist method.invoke(obj, temList); } catch (Exception e1) { log.info("【{}】方法執行失敗",method,e1); } } //獲取List的泛型參數類型 Type gType = field.getGenericType(); //判斷當前類型是否為參數化泛型 if (gType instanceof ParameterizedType) { //轉換成ParameterizedType對象 ParameterizedType pType = (ParameterizedType) gType; //獲得泛型類型的泛型參數(實際類型參數) Type[] tArgs = pType.getActualTypeArguments(); if (tArgs!=null&&tArgs.length>0) { //獲取當前元素的所有子元素 List<Element> elementSubList=e.elements(); //遍歷 for (Element e1:elementSubList) { try { //反射創建對象 Object tempObj = Class.forName(tArgs[0].getTypeName()).newInstance(); temList.add(tempObj); //遞歸調用自身 this.parseXml(e1, direction, tempObj); } catch (Exception e2) { log.error("【{}】對象構造失敗",tArgs[0].getTypeName(),e2); } } } } } else { //說明不是list標簽,繼續遞歸調用自身即可 this.parseXml(e, direction, obj); } } else if("1".equals(direction)) //說明轉換方向為:javabean轉xml { //遞歸調用自身 this.parseXml(e, direction, obj); } //此時還在for循環遍歷根元素的所有子元素 } } else { //說明無子節點 //獲取當前元素的名稱 String nodeName = element.getName(); //獲取當前元素的對應的值 String nodeValue = element.getStringValue(); //判斷轉換方向:1:java2xml、2:xml2java if ("1".equals(direction))//java2xml { if (nodeValue != null && nodeValue.matches(varRegex)) { /** * 獲取模板中各節點定義的變量名,例如<traceNo>${traceNo}</traceNo> */ nodeValue = nodeValue.substring(nodeValue.indexOf("${") + 2, nodeValue.indexOf("}")); Object value = null; //根據解析出的變量名,調用obj對象的getXXX()方法獲取變量值 Method method = this.getDeclaredMethod(obj, "get".concat(this.toUpperCaseFirstOne(nodeValue)), null); if (method != null) { try { value = method.invoke(obj); } catch (Exception e) { log.error("方法【{}】調用異常", "get".concat(this.toUpperCaseFirstOne(nodeValue))); } } //將變量值填充至xml模板變量名位置,例如<traceNo>${traceNo}</traceNo> element.setText(value == null ? "" : value.toString()); } //葉子節點 log.debug("節點名【{}】,節點變量名【{}】",element.getName(),nodeValue); } else if ("2".equals(direction))//xml2java { if (nodeName != null && !"".equals(nodeName)) { //根據xml節點名,調用obj對象的setXXX()方法為obj設置變量值 Method method = this.getDeclaredMethod(obj, "set".concat(this.toUpperCaseFirstOne(nodeName)), new Class[]{String.class}); if(method!=null) { try { method.invoke(obj, nodeValue); } catch (Exception e) { log.error("方法【{}】調用異常","set".concat(this.toUpperCaseFirstOne(nodeName))); } } } } } } private Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes) { for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { try { return superClass.getDeclaredMethod(methodName, parameterTypes); } catch (NoSuchMethodException e) { //Method 不在當前類定義, 繼續向上轉型 } //.. } return null; } private String toUpperCaseFirstOne(String s) { // 進行字母的ascii編碼前移,效率要高於截取字符串進行轉換的操作 char[] cs = s.toCharArray(); cs[0] -= 32; return String.valueOf(cs); } }
4.測試
1.准備請求實體bean
private static ReqSchool makeReq() { ReqSchool rspSchool = new ReqSchool(); //學校編號 rspSchool.setNumber("1001"); //學校名稱 rspSchool.setName("實驗小學"); //學校省份 rspSchool.setProvince("北京市"); //學校地區 rspSchool.setAddress("西城區"); //學生班級 rspSchool.setStuclass("高一(2)班"); //學生姓名 rspSchool.setStuname("張三"); //學生成績 rspSchool.setStuscore("92"); return rspSchool; }
2.main方法測試(請求)
public static void main(String[] args) { //定義請求模版 String requestXml="<?xml version=\"1.0\" encoding = \"GBK\"?>\n" + "<SCHOOL>\n" + "<Head>\n" + "<number>${number}</number>\n" + "<name>${name}</name>\n" + "<province>${province}</province>\n" + "<address>${address}</address>\n" + "</Head>\n" + "<Body>\n" + "<stuclass>${stuclass}</stuclass>\n" + "<stuname>${stuname}</stuname>\n" + "<stuscore>${stuscore}</stuscore>\n" + "</Body>\n" + "</SCHOOL>"; //這里我直接使用構造方法(實際開發應以線程安全的單例模式) XmlUtil xmlUtil = new XmlUtil(); //獲取document對象 Document document = xmlUtil.getDocument(requestXml); //獲取根元素 Element root = document.getRootElement(); //請求實體bean ReqSchool reqSchool = makeReq(); //解析xml,1:表示java2xml xmlUtil.parseXml(root,"1",reqSchool); //輸出請求報文 System.out.println(root.asXML()); }
查看控制台結果:
3.main方法測試(響應)
public static void main(String[] args) { //定義響應報文 String responseXML="<?xml version=\"1.0\" encoding = \"GBK\"?>\n" + "<SCHOOL>\n" + "<Head>\n" + "<number>0001</number>\n" + "<name>xxx實驗小學</name>\n" + "<province>北京市</province>\n" + "<address>西城區</address>\n" + "</Head>\n" + "<Body>\n" + " <students>\n" + " <student>\n" + " <stuclass>高三二班</stuclass>\n" + " <stuname>李四</stuname>\n" + " <stuscore>100</stuscore>\n" + " </student>\n" + " <student>\n" + " <stuclass>高三三班</stuclass>\n" + " <stuname>張三</stuname>\n" + " <stuscore>95</stuscore>\n" + " </student>\n" + " <student>\n" + " <stuclass>高三四班</stuclass>\n" + " <stuname>王五</stuname>\n" + " <stuscore>0</stuscore>\n" + " </student>\n" + " </students>\n" + "</Body>\n" + "</SCHOOL>"; //這里我直接使用構造方法(實際開發應以線程安全的單例模式) XmlUtil xmlUtil = new XmlUtil(); Document document = xmlUtil.getDocument(responseXML); Element rootElement = document.getRootElement(); RspSchool rspSchool = new RspSchool(); xmlUtil.parseXml(rootElement,"2",rspSchool); System.out.println(rspSchool); }
控制結果如下:
五、總結
1.dom4j解析xml的步驟
①獲取執行xml的輸入流
②創建xml讀取對象(SaxReader),用於讀取輸入流
③通過讀取對象(SaxReader)讀取xml的輸入流,獲取文檔對象(Document)
④通過文檔對象,得到整個文檔的 根元素對象(Element)
⑤通過根元素,得到其他層次的所有元素對象
2.反射
反射這里是重中之重,感謝大家的閱讀,如有問題,歡迎大家指正~