3
许多的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文档信息,而是直接使用信息。这也是非常大的收获呀。


炼金术师cck
94 声望34 粉丝

早睡早起