Java之XML解析

許多的Java框架都支持用戶自己配置,其中很常見的就是使用XML文件進行配置。
本篇講XML在Java中的解析,最後會簡單地講Mybatis在解析XML時的做法。

XML 文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql:///mybatis" />
                <property name="username" value="root" />
                <property name="password" value="121213" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/mapper/IUserMapper.xml" />
    </mappers>

</configuration>

XML 文件較爲常見的就是上邊的樣子

  1. 第一行是文檔頭
  2. 第二行是文檔類型定義(DTD,有時是Schema。作用都是爲了保證文檔正確)
  3. 其餘的就是元素

需要注意的地方

  1. XML 是大小寫敏感的
  2. XML 的屬性必須用引號括起來
  3. XML 所有屬性必須有值

Java DOM解析器

  1. Java讀入一個XML文件需要DocumentBuilder類,可以通過DocumentBuilderFactory類構建。

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();
  2. 之後就可以通過DocumentBuilder類的parse方法讀入一個XML文件啦。
    parse接受多種參數,如FileInputStream等。

    InputStream stream 
       = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
    Document document = builder.parse(stream);
       或        
    File file = new File("src/main/resources/mybatis-config.xml");
    Document document = builder.parse(file);
  3. 此時就已經可以使用了,需要注意的一點就是,元素之間的空白字符也會被認爲是子元素。
    在沒有使用DTD或Schema的情況下,需要我們手動判斷元素是否繼承自Element
    Node就是我們XML文件上的一個元素,Node類還有很多實用的方法,這裏就不一一列舉了。

    // 獲取根元素
    Element root = document.getDocumentElement();
    // 獲取孩子元素
    NodeList childNodes = root.getChildNodes();
    for (int i = 0; i < childNodes.getLength(); i++) {
        Node node = childNodes.item(i);
        if (node instanceof Element) {
            System.out.println(node.getNodeName() + " " + node.getTextContent());
        }
    }        
  4. 如果使用了XML校驗,也就是DTD或者Schema。在使用時可以進行設置。
    解析器通過解析校驗的文件,可以知道哪些元素沒有文本節點的子元素,因此可以幫我們剔除空白字符。

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    // 開啓校驗
    factory.setValidating(true);
    // 忽略空白字符
    factory.setIgnoringElementContentWhitespace(true);
  5. 有個地方需要特殊處理,如果解析的是一個流的話,即parse(inputStream)
    並且在我們的XML文件中使用的是DTD文件的相對路徑,
    則需要提供一個實體解析器,用於指定DTD文件。

    <!-- XML中指定DTD文件時使用了相對位置 -->
    <!DOCTYPE configuration SYSTEM "mybatis-config.dtd">      
    
    // 實體解析器
    public class MyEntityResolver implements EntityResolver {
        @Override
        public InputSource resolveEntity(
               String publicId, 
               String systemId) throws SAXException, IOException {
                   
           InputStream stream = Thread.currentThread()
                                      .getContextClassLoader()
                                      .getResourceAsStream("mybatis-config.dtd");
           return new InputSource(stream);
        }
    }
    
    // 構建Builder時,設置實體解析器
    DocumentBuilder builder = factory.newDocumentBuilder();
    builder.setEntityResolver(new MyEntityResolver());

Java XPath定位信息

在定位XML文件信息時,使用獲取元素,再判斷元素是否是目標元素的辦法非常痛苦。
Java 爲我們提供了好用的XPath類。

  1. 創建XPath對象

    XPathFactory xPathFactory = XPathFactory.newInstance();
    XPath xPath = xPathFactory.newXPath();
  2. 編寫表達式,調用evaluate方法求值

    // Document document = ...;
    // 獲取dataSource元素
    String expression1 = "/configuration/environments/environment/dataSource";
    Node node = (Node) xPath.evaluate(
                        expression1, 
                        document, 
                        XPathConstants.NODE);
    
    // 也可以在當前已獲得的節點下開始查找, 獲取dateSource的type屬性
    String type = xPath.evaluate("@type", node);
    
    // 獲取mappers下的第一個mapper子元素的resource屬性,注意!索引是從1開始的
    String expression2 = "/configuration/mappers/mapper[1]/@resource", document);
    String resource = xPath.evaluate(
                    expression2, 
                    document);               

Mybatis 解析XML

  1. XPathParser
    在mybatis中,解析XML使用了XPathParser類,這個類是mybatis自定義的,
    類中持有一個Document對象,是我們的XML文件,還有一個XPath對象。
    類中提供了定位信息的方法,使用的就是Java提供的XPath類。
    XPathParser解析出的元素用一個XNode對象存儲。

    public class XPathParser {
    
        private Document document;
        private boolean validation;
        private EntityResolver entityResolver;
        private Properties variables;
        private XPath xpath;
        //...
        
        public XNode evalNode(String expression) {
            return evalNode(document, expression);
        }
        
        public XNode evalNode(Object root, String expression) {
            Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
            if (node == null) {
                return null;
            }
            return new XNode(this, node, variables);
        }
    
        private Object evaluate(String expression, Object root, QName returnType) {
            try {
                return xpath.evaluate(expression, root, returnType);
            } catch (Exception e) {
                throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
            }
        }            
        //...
    }   
    
  2. XNode
    mybatis將Node類進一步封裝,用XNode表示。
    當構造XNode對象時,會自動解析出元素的元素名、元素的屬性等。
    此外XNode中提供了獲取子元素、獲取父元素等行爲,由於持有XPathParser對象,
    XNode中還提供了定位信息的方法。

    public class XNode {
    
        private Node node;
        private String name;
        private String body;
        private Properties attributes;
        private Properties variables;
        private XPathParser xpathParser;
        
        public XNode(XPathParser xpathParser, Node node, Properties variables) {
            this.xpathParser = xpathParser;
            this.node = node;
            this.name = node.getNodeName();
            this.variables = variables;
            this.attributes = parseAttributes(node);
            this.body = parseBody(node);
        }
        
        public XNode evalNode(String expression) {
            return xpathParser.evalNode(node, expression);
        }
        ...
    }
  3. mybatis中,獲取根元素只需這樣寫
    XNode root = xPathParser.evalNode("/configuration");
    之後獲取configuration元素下的mappers是這樣寫
    root.evalNode("mappers")
  4. DTD
    mybatis的XML文件使用了DTD,使用解析流的形式解析XML時,
    mybatis也提供了實體解析器XMLMapperEntityResolver,
    mybatis的DTD文件路徑是/org/apache/ibatis/builder/xml/mybatis-3-config.dtd

結語

瞭解Java提供的解析XML類,再去看各大框架如何解析XML就很容易了。
從這些框架中學習到如何封裝好解析的行爲,讓我們使用的過程中,
不必花費太多功夫去獲取XML文檔信息,而是直接使用信息。這也是非常大的收穫呀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章