dom4j+反射實現bean與xml的相互轉換


由於目前在工作中一直用的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.反射

  反射這里是重中之重,感謝大家的閱讀,如有問題,歡迎大家指正~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM