美文网首页MyBatis源码剖析
[MyBatis源码详解 - 解析器模块 - 组件一] XNod

[MyBatis源码详解 - 解析器模块 - 组件一] XNod

作者: 小胡_鸭 | 来源:发表于2019-07-25 00:17 被阅读0次
    一、功能

      常用的解析XML的方式有三种,详见:Java解析xml的三种方式,其中最常用的是DOM,在DOM中每个XML的节点都是一个Node(org.w3c.dom.Node),Node通常跟XPath配合使用,提供了解析节点元素名属性名属性值节点文本内容嵌套节点等功能,要想熟练地使用这些功能,需要对XPath表达式解析的语法非常熟悉。
      XNode封装了Node,提供了常见的解析一个Node节点需要的功能和方法。

    二、属性

      XNode的属性组成如图:

    XNode_field.jpg
      node:被包装的org.w3c.dom.Node对象
      name:节点名
      body:节点内容
      attributes:节点属性集合
      variables:mybatis-config.xml配置文件中<properties>节点下引入或定义的键值对
      xpathParser:封装了XPath解析器,XNode对象由XPathParser对象生成,并提供了解析XPath表达式的功能

    eg:

    <?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> 
        <properties>
            <property name="database.driver" value="com.mysql.jdbc.Driver"/>
            <property name="database.url" value="jdbc:mysql://localhost:3306/ssm?useSSL=false"/>
            <property name="database.username" value="root"/>
            <property name="database.password" value="root"/>
        </properties>
    
        <typeAliases> <!-- 别名 -->
            <typeAlias alias="role" type="com.learn.ssm.chapter4.pojo.Role"/>
        </typeAliases>
    </configuration>
    

      以<typeAlias>节点为例子,生成一个对应的XNode节点后,name值为"typeAlias",由于没有文本内容也没有子节点所以body为空,attributes值为{alias=role, type=com.learn.ssm.chapter4.pojo.Role}的一个Properties对象,variables{database.driver=com.mysql.jdbc.Driver, database.url=jdbc:mysql://localhost:3306/ssm?useSSL=false, database.username=root, database.password=root},xpathParser是构造函数传进来的一个参数,只需要知道它提供了解析XPath表达式的功能即可。

    三、构造函数
        public XNode(XPathParser xpathParser, Node node, Properties variables) {
            this.xpathParser = xpathParser;
            this.node = node;
            this.name = node.getNodeName();  // name是调用Node的方法获取
            this.variables = variables;      // variables是外部传入的构造参数
            this.body = parseBody(node);
            this.attributes = parseAttributes(node);
        }
    
    四、方法

      解析节点内容调用的是parseBody方法,先从该方法入手分析。

    1、private String parseBody(Node node)
        // 提供解析节点文本内容的功能
        private String parseBody(Node node) {
            String data = getBodyData(node);  
            if (data == null) {
                NodeList children = node.getChildNodes();
                for (int i = 0; i < children.getLength(); i++) {
                    Node child = children.item(i);
                    data = getBodyData(child);
                    if (data != null) {
                        break;
                    }               
                }           
            }
            return data;
        }
    

    【功能】提供解析节点文本内容的功能
    【源码分析】getBodyData方法可以获取节点文本内容,只有文本类型的节点才能返回字符串值,否则返回空,如果一个节点形如<A>aaa</A>,则返回"aaa",后面parseBody直接返回即可;如果非文本节点,则进入下面的if分支,分支中先去获取该节点下的所有子节点,并且逐个获取子节点的文本内容(假如子节点时文本节点的话),一旦获取到第一个文本子节点的文本内容,并且内容非空,则跳出循环返回,如下所示,A会在解析第二个子节点C时拿到其文本内容cbody跳出循环返回。

    <A>
      <B att="val"/>
      <C>cbody</C>
      <D>dbody</D>
    </A>
    
    2、private String getBodyData(Node child)
        private String getBodyData(Node child) {
            /**
             *  只处理文本类型的节点: Node.CDATA_SECTION_NODE、Node.TEXT_NODE
             *                 Node.COMMENT_NODE(不需要判断,因为XPathParser加载XML时已经设置了忽略注释)
             */
            if (child.getNodeType() == Node.CDATA_SECTION_NODE ||
                child.getNodeType() == Node.TEXT_NODE) {
                String data = ((CharacterData) child).getData();
                data = PropertyParser.parse(data, variables);
                return data;
            }
            return null;
        }
    

    【功能】获取文本节点文本内容
    【源码分析】代码很简单,PropertyParser.parse(data, variables);只是为了解析带占位符的变量的值,比如解析到的文本内容为${database.driver},则data会被进一步解析成com.mysql.jdbc.Driver,此处暂时不深究PropertyParser是怎么实现的。



      解析节点属性调用的是parseAttributes方法,接下来分析parseAttributes方法。

    3、private Properties parseAttributes(Node node)
        // 解析节点属性键值对,并将其放入Properties对象中,对外提供根据属性名差属性值功能时用到
        private Properties parseAttributes(Node node) {
            Properties attributes = new Properties();
            NamedNodeMap attributeNodes = node.getAttributes();
            if (attributeNodes != null) {
                for (int i = 0; i < attributeNodes.getLength(); i++) {
                    Node attribute = attributeNodes.item(i);
                    String value = PropertyParser.parse(attribute.getNodeValue(), variables);
                    attributes.put(attribute.getNodeName(), value);
                }
            }
            return attributes;
        }
    

    【功能】获取所有节点属性
    【源码分析】先通过Node.getAttributes()获取到了包含所有节点属性的NamedNodeMap对象,接着遍历该对象,拿到属性名和属性值,放入要返回的Properties对象中。

    4、public XNode getParent()
        // 获取当前节点的父节点
        public XNode getParent() {
            Node parent = node.getParentNode();
            if (parent == null || !(parent instanceof Element)) {
                return null;
            } else {
                return new XNode(xpathParser, parent, variables);
            }
        }
    

    【功能】获取当前节点的父节点并包装为XNode
    【源码分析】如果是顶层节点或非元素节点,则返回空,否则创建。

    5、public String getPath()
        // 获取节点路径
        public String getPath() {
            StringBuilder builder = new StringBuilder();
            Node current = node;
            while (current != null && current instanceof Element) {
                if (current != node) {
                    builder.insert(0, "/");
                }
                builder.insert(0, current.getNodeName());
                current = current.getParentNode();
            }
            return builder.toString();
        }
    

    【功能】获取节点路径
    【源码分析】获取从当前节点到顶层节点的路径,在while循环中每次current都会获取其父节点,一层层向上追溯,直到顶层节点,比如<A><B><C></C></B></A>,对C节点来说节点路径就是A/B/C

    6、public String getValueBasedIdentifier()
        // 获取节点值的识别码,优先级: id > value > property
        public String getValueBasedIdentifier() {
            StringBuilder builder = new StringBuilder();
            XNode current = this;
            while (current != null) {
                if (current != this) {
                    builder.insert(0, "_");
                }
                String value = current.getStringAttribute("id", 
                                current.getStringAttribute("value", 
                                 current.getStringAttribute("property", null)));
                if (value != null) {
                    value = value.replace('.', '_');
                    builder.insert(0, "]");
                    builder.insert(0, value);
                    builder.insert(0, "[");
                }
                builder.insert(0, current.getName());
                current = current.getParent();
            }
            return builder.toString();
        }
    

    【功能】获取节点值的识别码,优先级: id > value > property
    【源码分析】获取一个能唯一标识节点的字符串,如下面的C节点,返回的唯一标识字符串为A_B[bid]_C[cid],类似于获取节点路径,也会一层层追溯到顶层节点。

    <A>
      <B id="bid">
        <C id="cid" value="cvalue"/>
      </B>
    </A>
    


    6、eval*()系列方法
        public String evalString(String expression) {
            return xpathParser.evalString(node, expression);
        }
        
        public Boolean evalBoolean(String expression) {
            return xpathParser.evalBoolean(node, expression);
        }
        
        public Double evalDouble(String expression) {
            return xpathParser.evalDouble(node, expression);
        }
        
        public XNode evalNode(String expression) {
            return xpathParser.evalNode(node, expression);
        }
        
        public List<XNode> evalNodes(String expression) {
            return xpathParser.evalNodes(node, expression);
        }
    

    【功能】调用XPathParser方法在当前节点下寻找符合表达式条件的节点,通常是文本节点,并将其值转化为指定的类型,如果值无法转化为指定类型会报错。
    【支持数据类型】 String、Boolean、Double、Node、List<Node>
    【源码分析】简单调用XPathParser提供的方法

    7、get*Body()系列方法(以getBooleanBody为例)
        public Boolean getBooleanBody() {
            return getBooleanBody(null);
        }
        
        public Boolean getBooleanBody(Boolean def) {
            if (body == null) {
                return def;
            } else {
                return Boolean.valueOf(body);
            }
        }
    

    【功能】获取文本节点内容并将其转化为指定的数据类型
    【支持的数据类型】 String、Boolean、Integer、Long、Double、Float
    【源码分析】简单判断body是否为空,不为空则做类型转换

    7、get*Attribute()系列方法(以getStringAttribute为例)
        // 获取属性值,如果没有返回null
        public String getStringAttribute(String name) {
            return getStringAttribute(name, null);
        }
        
        // 获取属性是,没有没有使用默认值
        public String getStringAttribute(String name, String def) {
            String value = attributes.getProperty(name);
            if (value == null) {
                return def;
            } else {
                return value;
            }
        }
    

    【功能】获取节点指定属性的属性值并将其转化为指定的数据类型
    【支持的数据类型】 Enum、String、Boolean、Integer、Long、Double、Float
    【源码分析】从attributes中根据属性名取出属性值再坐简单类型转化

    8、public List<XNode> getChildren()
        // 获取子节点,对Node.getChildNodes()做相应的封装得到List<XNode>
        public List<XNode> getChildren() {
            List<XNode> children = new ArrayList<XNode>();
            NodeList nodeList = node.getChildNodes();
            if (nodeList != null) {
                // 我的写法
                /*for (int i = 0; i < nodeList.getLength(); i++) {
                    Node node = nodeList.item(i);
                    if (node instanceof Element) {
                        children.add(new XNode(xpathParser, node, variables));
                    }
                }*/
                
                // 源码的写法
                for (int i = 0, n = nodeList.getLength(); i < n; i++) {
                    Node node = nodeList.item(i);
                    if (node.getNodeType() == Node.ELEMENT_NODE) {
                        children.add(new XNode(xpathParser, node, variables));
                    }
                }
            }
            return children;
        }
    

    【功能】获取子节点,对Node.getChildNodes()做相应的封装得到List<XNode>
    【源码分析】取出包装的Node对象的所有子元素节点,并对其包装成XNode列表返回

    9、public Properties getChildrenAsProperties()
        // 获取所有子节点的name、value属性键值对
        public Properties getChildrenAsProperties() {
            Properties properties = new Properties();
            for (XNode child : getChildren()) {
                String name = child.getStringAttribute("name");
                String value = child.getStringAttribute("value");
                if (name != null && value != null) {
                    properties.put(name, value);
                }
            }
            return properties;
        }
    

    【功能】获取所有子节点的name、value属性键值对
    【源码分析】先调用getChildren()获得节点的所有子XNode,然后逐一遍历获取其name、value,放在Properties对象中返回。

    五、测试案例
    1、测试XML文件
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration attr1="haha" attr2="hehe">
        <evalType name="name1" value="value1">
            <evalString1>evalString1_text</evalString1>
            <evalString2>${evalString2}</evalString2>
            <evalBoolean>false</evalBoolean>
            <evalDouble>3.14156</evalDouble>
            <evalNode id="id">Node</evalNode>
            <evalNode2 id="id" value="value" property="property">Node</evalNode2>
            <evalNode3 value="value" property="property">Node</evalNode3>
            <evalNode4 property="property">Node</evalNode4>
            <evalInt>100</evalInt>
            <evalLong>20000000000</evalLong>
            <evalFloat>6.667</evalFloat>
        </evalType>
        
        <getTypeAttribute name="name2" value="value2">
            <getStringAttribute value="string"/>
            <getBooleanAttribute value="false"/>
            <getIntAttribute value="100"/>
            <getLongAttribute value="2000000000"/>
            <getDoubleAttribute value="3.1415"/>
            <getFloatAttribute value="6.667"/>
            <style font="Aharoni"/>
        </getTypeAttribute>
        
    </configuration>
    
    2、测试案例
    package org.apache.ibatis.parsing.test;
    
    import java.io.BufferedInputStream;
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.util.List;
    import java.util.Properties;
    
    import org.apache.ibatis.parsing.XNode;
    import org.apache.ibatis.parsing.XPathParser;
    
    enum Font { Aharoni, Aldhabi, Algerian }
    
    public class XNodeTest {
        public static void main(String[] args) throws Exception {
            // 1. 加载XML文件
            InputStream inputStream = new BufferedInputStream(new FileInputStream("src/main/java/org/apache/ibatis/parsing/test/XNodeTest.xml"));
            
            // 2. 生成XPathParser对象
            Properties variables = new Properties();
            variables.put("evalString2", "evalString2_text");
            XPathParser parser = new XPathParser(inputStream, false, variables, null);
            
            // 3. 生成XNode对象
            XNode configuration = parser.evalNode("/configuration");
            System.out.println("getName(): " + configuration.getName());
            System.out.println("toSting(): " + configuration);
            
            // 4. 测试XNode的eval*()系列方法
            System.out.println("【testEvalType】");
            testEvalType(configuration);
            
            //  5. 节点路径、父节点
            System.out.println("【getPath()、getParent()】");
            XNode evalNode =  configuration.evalNode("evalType/evalNode");
            System.out.println("getPath(): " + evalNode.getPath());
            XNode evalNodeParent = evalNode.getParent();
            System.out.println("getParent(): " + evalNodeParent);
            
            // 6. 节点识别码
            System.out.println("【getValueBasedIdentifier(): id > value > property】");
            XNode evalNode2 =  configuration.evalNode("evalType/evalNode2");
            XNode evalNode3 =  configuration.evalNode("evalType/evalNode3");
            XNode evalNode4 =  configuration.evalNode("evalType/evalNode4");
            System.out.println("getValueBasedIdentifier(): [property] " + evalNode4.getValueBasedIdentifier());
            System.out.println("getValueBasedIdentifier(): [value > property] " + evalNode3.getValueBasedIdentifier());
            System.out.println("getValueBasedIdentifier(): [id > value > property] " + evalNode2.getValueBasedIdentifier());
            System.out.println();
            
            // 7. 测试get*Body()系列方法
            System.out.println("【testEvalBody】");
            testGetTypeBody(configuration.evalNode("evalType"));
            System.out.println();
            
            // 8. 测试get*Attribute()系列方法
            System.out.println("【testGetTypeAttribute】");
            testGetTypeAttribute(configuration.evalNode("getTypeAttribute"));
            System.out.println();
            
            // 9. getChildren()
            System.out.println("【getChildren()】");
            List<XNode> children = configuration.getChildren();
            System.out.println("children num is " + children.size());
            for (XNode node : children) {
                System.out.println(node.getPath());         
            }
            System.out.println();
            
            // 10. getChildrenAsProperties()
            System.out.println("【getChildrenAsProperties】");
            Properties properties = configuration.getChildrenAsProperties();
            System.out.println(properties);
            System.out.println();
            
            // 11. parseAttributes()
            System.out.println("【parseAttributes()】");
            System.out.println(configuration.getStringAttribute("attr1"));
            System.out.println(configuration.getStringAttribute("attr2"));
        }
        
        public static void testEvalType(XNode root) {
            String evalString1 = root.evalString("evalType/evalString1/text()");
            String evalString2 = root.evalString("evalType/evalString2/text()");
            Boolean evalBoolean = root.evalBoolean("evalType/evalBoolean/text()");
            Double evalDouble = root.evalDouble("evalType/evalDouble/text()");
            XNode evalNode =  root.evalNode("evalType/evalNode");
            List<XNode> evalNodes = root.evalNodes("evalType/*");
            
            System.out.println("evalString(expr): " + evalString1);
            System.out.println("evalString(expr) with var: " + evalString2);
            System.out.println("evalBoolean(expr): " + evalBoolean);
            System.out.println("evalDouble(expr): " + evalDouble);
            System.out.println("evalNode(expr): " + evalNode);
            System.out.println("evalNodes(expr): nodeList size is " + evalNodes.size());
            for (int i = 0; i < evalNodes.size(); i++) {
                System.out.println("nodeList[" + i + "] = " + evalNodes.get(i));
            }
        }
        
        public static void testGetTypeBody(XNode root) {        
            XNode node = root.evalNode("evalString1/text()");
            XNode node1 = root.evalNode("evalBoolean/text()");
            XNode node2 = root.evalNode("evalInt/text()");
            XNode node3 = root.evalNode("evalLong/text()");
            XNode node4 = root.evalNode("evalDouble/text()");
            XNode node5 = root.evalNode("evalFloat/text()");
            String getStringBody = node.getStringBody();
            Boolean getBooleanBody = node1.getBooleanBody();
            Integer getIntBody = node2.getIntBody();
            Long getLongBody = node3.getLongBody();
            Double getDoubleBody = node4.getDoubleBody();
            Float getFloatBody = node5.getFloatBody();
            
            System.out.println("getStringBody(): " + getStringBody);
            System.out.println("getBooleanBody(): " + getBooleanBody);
            System.out.println("getIntBody(): " + getIntBody);
            System.out.println("getLongBody(): " + getLongBody);
            System.out.println("getDoubleBody(): " + getDoubleBody);
            System.out.println("getFloatBody(): " + getFloatBody);
        }
        
        public static void testGetTypeAttribute(XNode root) {
            XNode node = root.evalNode("getStringAttribute");
            XNode node1 = root.evalNode("getBooleanAttribute");
            XNode node2 = root.evalNode("getIntAttribute");
            XNode node3 = root.evalNode("getLongAttribute");
            XNode node4 = root.evalNode("getDoubleAttribute");
            XNode node5 = root.evalNode("getFloatAttribute");
            XNode node6 = root.evalNode("style");
            
            System.out.println("getStringAttribute(name): " + node.getStringAttribute("value"));
            System.out.println("getBooleanAttribute(name): " + node1.getBooleanAttribute("value"));
            System.out.println("getIntAttribute(name): " + node2.getIntAttribute("value"));
            System.out.println("getLongAttribute(name): " + node3.getLongAttribute("value"));
            System.out.println("getDoubleAttribute(name): " + node4.getDoubleAttribute("value"));
            System.out.println("getFloatAttribute(name): " + node5.getFloatAttribute("value"));
            System.out.println("getEnumAttribute(Class, name): " + node6.getEnumAttribute(Font.class, "font"));
        }
    }
    
    3、输出结果
    #consoles
    
    getName(): configuration
    toSting(): <configuration attr2="hehe" attr1="haha">
    <evalType name="name1" value="value1">
    <evalString1>evalString1_text</evalString1>
    <evalString2>evalString2_text</evalString2>
    <evalBoolean>false</evalBoolean>
    <evalDouble>3.14156</evalDouble>
    <evalNode id="id">Node</evalNode>
    <evalNode2 value="value" property="property" id="id">Node</evalNode2>
    <evalNode3 value="value" property="property">Node</evalNode3>
    <evalNode4 property="property">Node</evalNode4>
    <evalInt>100</evalInt>
    <evalLong>20000000000</evalLong>
    <evalFloat>6.667</evalFloat>
    </evalType>
    <getTypeAttribute name="name2" value="value2">
    <getStringAttribute value="string"/>
    <getBooleanAttribute value="false"/>
    <getIntAttribute value="100"/>
    <getLongAttribute value="2000000000"/>
    <getDoubleAttribute value="3.1415"/>
    <getFloatAttribute value="6.667"/>
    <style font="Aharoni"/>
    </getTypeAttribute>
    </configuration>
    
    【testEvalType】
    evalString(expr): evalString1_text
    evalString(expr) with var: evalString2_text
    evalBoolean(expr): true
    evalDouble(expr): 3.14156
    evalNode(expr): <evalNode id="id">Node</evalNode>
    
    evalNodes(expr): nodeList size is 11
    nodeList[0] = <evalString1>evalString1_text</evalString1>
    
    nodeList[1] = <evalString2>evalString2_text</evalString2>
    
    nodeList[2] = <evalBoolean>false</evalBoolean>
    
    nodeList[3] = <evalDouble>3.14156</evalDouble>
    
    nodeList[4] = <evalNode id="id">Node</evalNode>
    
    nodeList[5] = <evalNode2 value="value" property="property" id="id">Node</evalNode2>
    
    nodeList[6] = <evalNode3 value="value" property="property">Node</evalNode3>
    
    nodeList[7] = <evalNode4 property="property">Node</evalNode4>
    
    nodeList[8] = <evalInt>100</evalInt>
    
    nodeList[9] = <evalLong>20000000000</evalLong>
    
    nodeList[10] = <evalFloat>6.667</evalFloat>
    
    【getPath()、getParent()】
    getPath(): configuration/evalType/evalNode
    getParent(): <evalType name="name1" value="value1">
    <evalString1>evalString1_text</evalString1>
    <evalString2>evalString2_text</evalString2>
    <evalBoolean>false</evalBoolean>
    <evalDouble>3.14156</evalDouble>
    <evalNode id="id">Node</evalNode>
    <evalNode2 value="value" property="property" id="id">Node</evalNode2>
    <evalNode3 value="value" property="property">Node</evalNode3>
    <evalNode4 property="property">Node</evalNode4>
    <evalInt>100</evalInt>
    <evalLong>20000000000</evalLong>
    <evalFloat>6.667</evalFloat>
    </evalType>
    
    【getValueBasedIdentifier(): id > value > property】
    getValueBasedIdentifier(): [property] configuration_evalType[value1]_evalNode4[property]
    getValueBasedIdentifier(): [value > property] configuration_evalType[value1]_evalNode3[value]
    getValueBasedIdentifier(): [id > value > property] configuration_evalType[value1]_evalNode2[id]
    
    【testEvalBody】
    getStringBody(): evalString1_text
    getBooleanBody(): false
    getIntBody(): 100
    getLongBody(): 20000000000
    getDoubleBody(): 3.14156
    getFloatBody(): 6.667
    
    【testGetTypeAttribute】
    getStringAttribute(name): string
    getBooleanAttribute(name): false
    getIntAttribute(name): 100
    getLongAttribute(name): 2000000000
    getDoubleAttribute(name): 3.1415
    getFloatAttribute(name): 6.667
    getEnumAttribute(Class, name): Aharoni
    
    【getChildren()】
    children num is 2
    configuration/evalType
    configuration/getTypeAttribute
    
    【getChildrenAsProperties】
    {name2=value2, name1=value1}
    
    【parseAttributes()】
    haha
    hehe
    


    六、我的github(注释源码、测试案例)

       [仓库地址] huyihao/mybatis-source-analysis
       [注释源码] XNode.java
       [测试案例源码] XNodeTest.java

    相关文章

      网友评论

        本文标题:[MyBatis源码详解 - 解析器模块 - 组件一] XNod

        本文链接:https://www.haomeiwen.com/subject/gdfqrctx.html