引言:我寫本文的宗旨在於給需要使用XML,而又對XML不是很熟悉的人們提供一種使用思路,而不沒有給出具體的使用方法,至於下文中提到的使用方法,還未嘗試過,都是從網上整理而來!
一、概述
什么是XML?
xm是l可擴展標記語言縮寫,xml是互聯網數據傳輸的重要工具,它可以跨越互聯網任何的平台,不受編程語言和
操作系統的限制,可以說它是一個擁有互聯網最高級別通行證的數據攜帶者。xml是當前處理結構化文檔信息中相當
給力的技術,xml有助於在服務器之間穿梭結構化數據,這使得開發人員更加得心應手的控制數據的存儲和傳輸。
什么是DTO?
DTD的作用是定義XML的合法構建模塊,它使用一系列的合法元素來定義文檔結構。
什么是XML Schema?
XML Schema是對XML文檔結構的定義和描述,其主要的作用是用來約束XML文件,並驗證XML文件有效性。
由於XML中可以使用自己定義的元素,由於是自己定義的,當給別人使用的時候,別人怎么知道你的XML文件中寫是什么東西呢?
這就需要在XML Scheme文件中聲明你自己定義了些什么元素,這些元素都有些什么屬性。
Schema與DTO的區別:
(1) Schema本身也是XML文檔,DTD定義跟XML沒有什么關系,Schema在理解和實際應用有很多的好處。
(2) DTD文檔的結構是“平鋪型”的,如果定義復雜的XML文檔,很難把握各元素之間的嵌套關系;Schema文檔結構性強,各元素之間的嵌套關系非常直觀。
(3) DTD只能指定元素含有文本,不能定義元素文本的具體類型,如字符型、整型、日期型、自定義類型等。Schema在這方面比DTD強大。
(4) Schema支持元素節點順序的描述,DTD沒有提供無序情況的描述,要定義無序必需窮舉排列的所有情況。Schema可以利用xs:all來表示無序的情況。
(5) 對命名空間的支持。DTD無法利用XML的命名空間,Schema很好滿足命名空間。並且,Schema還提供了include和import兩種引用命名空間的方法。
SAX解析:
SAX,全稱Simple API for XML,是一種以事件驅動的XMl API,是XML解析的一種新的替代方法,解析XML常用的還有DOM解析,PULL解析(Android特有),SAX與DOM不同的是它邊掃描邊解析,自頂向下依次解析,由於邊掃描邊解析,所以它解析XML具有速度快,占用內存少的優點,對於Android等CPU資源寶貴的移動平台來說是一個巨大的優勢。
Java JDK自帶的解析(SAXParserFactory SAXPaeser DefaultHandler)
特點: 一行一行的往下面執行解析的
二、使用步驟及案例
1. SAX解析步驟
解析步驟: 1.創建一個SAXParserFactory對象 SAXParserFactory factory=SAXParserFactory.newInstance(); 2.獲得解析器 SAXParser parser=factory.newSAXParser(); 3.調用解析方法解析xml,這里的第一個參數可以傳遞文件、流、字符串、需要注意第二個參數(new DefaultHander) File file=new File("girls.xml"); parser.parse(file,new DefaultHandler()); /**注解:--->這里的DefaultHandler表示 DefaultHandler類是SAX2事件處理程序的默認基類。它繼承了EntityResolver、DTDHandler、 ContentHandler和ErrorHandler這四個接口。包含這四個接口的所有方法,所以我們在編寫事件處理程序時, 可以不用直接實現這四個接口,而繼承該類,然后重寫我們需要的方法,所以在這之前我們先定義一個用於實現解析 方法如下:*/ 4.創建一個MyHandler類來繼承DefaultHandler並重寫方法 //定一個名為MyHandler類用來繼承DefaultHandler (1)MyHandler extends DefaultHander (2)重寫方法,快速記住方法(2個開始,2個結束,1一個文字(charactor--里面的內容)) (3)2個開始:StartDocment(文檔的開始)StartElement(元素的開始) 2個結束:endElement(元素的結束) endDocment(文檔的結束,標志着xml文件的結束) 1個文字內容:charactor(文字內容) 5.創建一個集合把所解析的內容添加到集合 //分析:目的我們只是需要把xml里面的文字內容添加到我們的集合而不需要其他元素,所以我們需要進行判斷得到 //(接上)我們需要的內容(下面會賦一個圖幫助理解) 6.接步驟三 輸出集合System.out.pritnln(list); 解析完成!
解析流程圖:
(1) 待解析的xml
<?xml version="1.0" encoding="utf-8" ?> <girls> <girl id="1"> <name>黃夢瑩</name> <age>32</age> </girl> <girl id="2"> <name>劉亦菲</name> <age>33</age> </girl> </girls>
(2) 用於封裝數據的實體
/** * 封裝實體 * * @author zls * @date 2020/3/19 */ @Data public class Girl { private String id; private String name; private String age; }
(3) 自定義解析xml的類

package sax; import lombok.Data; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * 自定義xml文件解析的實現類 * 原理:sax是一行一行的往下面執行解析的 * * 參考:https://blog.csdn.net/lsh364797468/article/details/51325540 * @author zls * @date 2020/3/19 */ @Data public class MyHandler extends DefaultHandler { // 准備一個用於添加xml數據的集合、調用Girl類、准一個用於用來保存開始的標簽的tag private List<Girl> girls; private Girl girl; private String tag; // 記錄xml中元素表中,因為是一行一行的解析的,所以需要記錄 /** * 文檔的開始 * @throws SAXException */ @Override public void startDocument() throws SAXException { super.startDocument(); // 因為這個方法只調用一次,所以在開始的時候就可以實例化集合 girls = new ArrayList<>(); } /** * 文檔結束 * @throws SAXException */ @Override public void endDocument() throws SAXException { super.endDocument(); } /** * 元素的開始 * @param uri * @param localName * @param qName 每次遍歷取得的標簽,每次只取一行 * @param attributes * @throws SAXException */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); // 這個方法,只有當開始一個元素的時候才會調用, // 通過分析,當外部開始元素為girl的時候,需要將girl實例化 // 將tag賦值 tag = qName; if ("girl".equals(qName)) { girl = new Girl(); } } /** * 元素的結束 * @param uri * @param localName * @param qName * @throws SAXException */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); // 這句話,必須寫,因為,當sax解析完一個元素的時候,會自動認為換行符是一個字符,會繼續執行 character 方法 。如果不寫,就會造成沒有數據的現象。 tag = ""; // 這個方法,當到了元素結尾的時候,會調用,應該在這里,將對象添加到集合里面去。 if ("girl".equals(qName)) { girls.add(girl); } } /** * 文字內容 * @param ch * @param start * @param length * @throws SAXException */ @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); // 這里是內容,但是,無法直接判斷屬於哪一個元素。 String string = new String(ch, start, length); //這兩種情況,表示 當前語句執行在 girls 標簽內。 if ("name".equals(tag)) {//判斷當前內容,屬於哪一個元素。 girl.setName(string); } else if ("age".equals(tag)) { girl.setAge(string); } // try { // setFiledValue(tag, string); // } catch (IllegalAccessException e) { // e.printStackTrace(); // } } /** * 使用反射的方式設置字段的值 * * (1) 字段很多的情況 * 問題: * 如果xml中的標簽很多,即:類中的屬性很多,那么采用以上的方式,難道要寫十萬個if嗎? * 所以這里采用反射的方式,自動的給對象的屬性設置上值(學了那么多年的反射終於有了用武之地了)。 * * (2) 類中的屬性和xml中的標簽有點不一樣的情況 * 問題:我遇到的情況是第三方提供的xml里面所有標簽都是大寫字母,而我們項目字段采用的駝峰命名法,那么如果直接把xml中的標簽 * 定義為屬性名,那多難看啊,幾個單詞連在一起都是大寫,除了開發的人員自己能看懂以外,以后維護的人員看到了肯定會問候你祖上十八代的。 * 解決:為此,我采用了駝峰命名的方式給實體屬性命名,反射的時候可以轉換為大寫字母和xml中的標簽比較 * * (3) 除了用代理的方式,還可以用json轉對象的方式 * 我們可以手動的拼接成一個json字符串,然后再采用json轉對象的方式也是可以的。這種方式理論上也是可以的,有興趣可以自己實現以下。 * @param tag * @param value */ public void setFiledValue(String tag, String value) throws IllegalAccessException { if (girl == null) { return; } Field[] declaredFields = girl.getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { if (Objects.equals(declaredField.getName(), tag)) { // 修改字段訪問控制權限(字段是私有的,則需要設為可以訪問) if(!declaredField.isAccessible()){ declaredField.setAccessible(true); } // 字段類型 String fieldType = declaredField.getType().getSimpleName(); System.out.println("字段:"+ declaredField.getName()+", 字段類型為: " + fieldType); declaredField.set(girl, value); } } } }
(4) 測試類

/** * @author zls * @date 2020/3/19 */ public class SaxPareTest { // 獲取test下的resource路徑,在測試文件夾下測試時,非類文件必須放在resource及其子文件夾下,要不然編譯之后文件就沒了 private static String path = SaxPareTest.class.getClass().getResource("/").getPath(); public static void main(String[] args) { // 1.創建對象 SAXParserFactory newInstance = SAXParserFactory.newInstance(); try { // 2.獲取解析器 SAXParser saxParser = newInstance.newSAXParser(); // 3.調用方法開始解析xml // 這里的路徑到時候根據自己的需要去拼,只要能找到文件即可 String absolutePath = path + "girls.xml"; File file = new File(absolutePath); MyHandler dh = new MyHandler(); saxParser.parse(file, dh); List<Girl> girls = dh.getGirls(); // 4.輸出集合 System.out.println(girls); // [Girl(id=null, name=黃夢瑩, age=32), Girl(id=null, name=劉亦菲, age=33)] } catch (ParserConfigurationException | SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
2. 解析XML文件(XML的解析使用 - 消費者)
在比較大的項目中,我們有時會用到服務這個概念,一些服務會以xml的形式返回結果,這個時候就要對XML進行解析,
但很多時候,我們對服務提供的XML結構不甚了解,就算了解了,如果服務被修改XML結構被改變,這個時候以前寫好
的解析XML的方法就會出現紊亂,如何解決這個問題呢?其實標准的服務在提供給用戶XML的時候會提供給用戶對應的
XML描述文件,這就是XSD文件,對此文件進行解析后再利用解析后的XSD文件對XML進行解析,這樣即使服務節點變了,
后台的代碼也能正確解析當前服務返回的XML文件。
使用方法,參考(案例1):
https://luo-yifan.iteye.com/blog/2036072
我們在使用XSD文件的時候,可以通過cmd命令的形式,將XSD文件生成java文件,生成方法參考:
另外附上一篇寫的比較好的xml文件解析的基礎教程,參考:
案例2:解析XML文件中的數據,然后將解析后的數據保存到數據庫
參考:
2. 生成XML文件(生產者)
案例1:java制作xml文件
參考:java生成XML文件
案例2:根據xsd定義的xml格式封裝數據並生成xml文件
參考:https://zmft1984.iteye.com/blog/798384
背景:
在項目組最近一次的版本開發中,有個需求是將我們項目數據庫中的基礎數據生成到xml文件中,並通知第三方獲取這個xml文件。
剛開始是想將數據按照xml的格式拼接成字符串,並逐行的寫到文件中,但是總覺得這樣做和java的面向對象格格不入。於是baidu了一
下,果然有比拼接字符串更好的辦法,具體實現步驟如下:
(1) 將要生成的xml文件的格式編寫到xsd文件中,如我們項目中用到的Channel.xsd;
(2) 借助於jdk的xjc命令,將xml中的節點轉換成java對象類以供數據封裝,具體實現是編寫一個bat文件,直接執行bat文件即可;
(3) 准備數據封裝,將從數據庫中查詢出來的數據按照xml格式從底層節點開始,逐層向上封裝;
(4) 使用jdk的JAXBContext將封裝好的數據輸出到xml文件。
案例3:根據xsd定義的xml格式封裝數據並生成xml文件
背景概述:
由於很多廠家在開發同一套系統,而我們公司只是參與了系統中的一部分,因此別的公司要求我們公司
將系統中某幾個模塊功能的數據封裝成為一個xml文件(當系統中這幾個模塊點擊添加,然后是填寫各種數據,
最后保存表單提交時需要生產一個xml文件),然后上傳到ftp服務器,其他公司給我們提供了一個XSD文件,
里面描述了對生產XML文件的要求,同時也還提供了一個word文檔說明哪個元素里面放什么內容。
開發步驟:
其實,這個需求就相當於案例2中的一樣,說白了就是要我們根據xsd定義的xml格式封裝數據,並生產xml文件。
但是感覺使用案例2中的方式太過去麻煩,於是就想有沒沒有什么簡單的方式呢?於是:
由於公司使用的是springboot框架,里面有一個東西叫thymeleaf,而我們可以自己定義模板,於是我們基於模板去生成
xml文件。
具體步驟:
(1) 更具提供的xsd文檔,自己制作一個html模板,並將自己制作的這個模板和springboot集成;
(2) 將自己系統中的業務數據傳遞給這個模板,然后通過thymeleaf中的表達式寫到這個html模板上(類似於我們平時渲染html頁面一樣);
(3) 將渲染完的這個模板后綴修改為.xml,然后通過ftp上傳到別的公司要求的目標地址上。
具體代碼的話后期在提供:
代碼業務方法:

/** * 測試類 */ public class Test{ @Autowired private IUserService userService; @Test public void test(){ //這里對應的是user模板.html頁面 ModelAndView view = new ModelAndView("user模板"); view.addObject("username", "gaigai"); view.addObject("age", 23); userService.upload(view,"user自動發布文件.xml"); } }
UserServiceImpl中的給springboot添加模板的方法:
springboot中模板引擎的使用,可以參考:thymeleaf模板引擎入門(當然,這個參考文檔沒有用springboot,
如果用springboot的話,集成起來應該更方便!)

public void upload(ModelAndView view, String filename) { FtpClient ftpClient = null; try { //Context用於保存模板中需要的一些變量。例如要把變量傳遞到模板中, //就可以先把變量放入IContext的實現類中,然后在模板中獲取該變量的值。 Context context = new Context(Locale.CHINESE,view.getModel()); //process方法用於解析模板並利用當前response對象的writer把模板輸出到瀏覽器。 String template = templateEngine.process(view.getViewName(), context); //通過ftp服務器上傳到指定的目錄 ByteArrayInputStream bais = new ByteArrayInputStream(template.getBytes("UTF-8")); ftpClient = ftpSource.borrowObject(); ftpClient.putFile(filename,bais); } catch (Exception e) { //"獲取ftp連接異常! } finally { if(ftpClient != null){ ftpSource.returnObject(ftpClient); //org.apache.commons.pool2 jar包 } } }
user模板.html 頁面為:

<?xml version="1.0" encoding="UTF-8" ?> <user xmlns:th="http://www.thymeleaf.org"> <username th:text="${username}">花千骨</username> <age th:text="${age}">32</age> </user>
我這里最終生成的 user自動發布文件.xml 和這里的html頁面內容一致,只不過是里面的數據被替換了。
參考: