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

[MyBatis源码详解 - 解析器模块 - 组件二] XPat

作者: 小胡_鸭 | 来源:发表于2019-07-25 10:58 被阅读0次
    一、属性

      XPathParser核心功能是封装了XPath,对表达式进行解析,并转化成为指定的数据类型,其属性如下:

        private Document document;
        private boolean validation;
        private EntityResolver entityResolver;
        private Properties variables;
        private XPath xpath;
    

      document:要解析的xml文件被转化成的Document对象。
      validation:获取document时是否要开启校验,开启校验的话会根据xml配置文件中定义的dtd文件校验xml格式,默认不开启校验。
      entityResolver:实体解析器,用于从本地工程目录中引入dtd文件,而不是根据网络URL去加载校验文件。
      variables:mybatis-config.xml配置文件中,<Properties>节点引入或定义的属性。
      xpath:封装的XPath对象,用来解析表达式。

    二、构造函数

      XPathParser提供了非常丰富的各种构造函数,支持通过多种形式来解析xml文件,可以传入xml的路径,可以先将xml处理成ReaderInputStream的形式,也可以将xml直接处理成Document,所有构造函数的参数列表如下:

    XPath_constructs.png
      所有构造函数做的事都如下所示:
        public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
            commonConstructor(validation, variables, null);
            this.document = createDocument(new InputSource(inputStream));
        }
    

      commonConstruct()方法初始化了除Document之外的其他类属性,XPath是通过XPathFactory创建的,源码如下:

        // 2. 初始化对象成员
        private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
            this.validation = validation;
            this.variables = variables;
            this.entityResolver = entityResolver;
            XPathFactory factory = XPathFactory.newInstance();
            this.xpath = factory.newXPath();
        }
    

      createDocument()方法源码如下:

        // 3. 将InputSource对象转化为Document对象
        private Document createDocument(InputSource inputSource) {
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                factory.setValidating(validation);   // 是否要校验由外界传入的值确定
                factory.setNamespaceAware(false);    // 如果要使用mybatis的XSD Schema,此处必须设为true,但源码里是false,说明官方默认用dtd来做校验,舍弃了XSD Schema
                factory.setIgnoringComments(true);   // 默认忽略注释
                factory.setIgnoringElementContentWhitespace(false);  // 只有开启校验才能去除xml中的空白节点,但是不知是否开启校验,所以这里设为了false
                factory.setCoalescing(false);
                factory.setExpandEntityReferences(true);    // 默认开启使用扩展实体引用
                
                DocumentBuilder builder = factory.newDocumentBuilder();
                builder.setEntityResolver(entityResolver);  // 使用传入的EntityResolver对象
                builder.setErrorHandler(new ErrorHandler() {  // 定义解析xml文档的错误处理器,如果发生了错误或致命错误则直接抛出异常,如果是警告默认不做处理
                    @Override
                    public void error(SAXParseException exception) throws SAXException {
                        throw exception;
                    }
                    
                    @Override
                    public void fatalError(SAXParseException exception) throws SAXException {
                        throw exception;                    
                    }               
                    
                    @Override
                    public void warning(SAXParseException exception) throws SAXException {}
                });
                return builder.parse(inputSource);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    

    【源码解析】
      要解析一个xml文件为Document,需要DocumentBuilder类的支持,实际上DocumentBuilder本身就支持多种形式的xml的解析(可看末尾的附录),这里之所以先统一转成InputSource,大概是为了代码简洁一点吧!
      DocumentBuilder对象由DocumentBuilderFactory创建,DocumentBuilderFactory可以设置是否校验、是否开启命名空间、是否忽略注释空白节点等,详细可以看上面源码注释。
      如果开启了xml格式校验,DocumentBuilder就要设置实体解析器,这里使用了构造函数传入的实体解析器,如果解析xml过程中检测到格式不对或者其他报错,则需要抛出异常信息,所以这里又设置了错误处理器ErrorHandler,对于错误和致命错误直接抛出异常,对于警告则忽略。

    三、方法功能

      通过上面的构造,接下来可以根据表达式解析获取document中的特定节点了,XPathParser提供了eval() 系列方法,根据需要调用指定类型的eval方法,所有的eval()方法都是对evaluate()方法返回值的处理,该方法中调用了XPath解析表达式,源码如下:

        private Object evaluate(String expression, Object root, QName returnType) {
            try {
                return xpath.evaluate(expression, root, returnType);
            } catch (Exception e) {         
                e.printStackTrace();
            }
            return null;
        }
    

      大多数eval*()都是返回一个常规Java数据类型的包装类,eg:

        public Boolean evalBoolean(String expression) {
            return evalBoolean(document, expression);
        }
        
        public Boolean evalBoolean(Object root, String expression) {
            return (Boolean) evaluate(expression, root, XPathConstants.BOOLEAN);
        }
    

      evalString()稍有特殊,多了个解析占位符的不走,在[MyBatis源码详解 - 解析器模块 - 组件一] XNode中第四节分析getBodyData()方法时已有说明,源码如下:

        // [start]
        public String evalString(String expression) {
            return evalString(document, expression);
        }
        
        // 解析节点占位变量的实际值
        // eg: ${database.password}
        public String evalString(Object root, String expression) {
            String result = (String) evaluate(expression, root, XPathConstants.STRING);
            result = PropertyParser.parse(result, variables);
            return result;
        }
    

      XPathParser还支持解析文档中的节点并将其封装成XNode对象,如果表达式表示的是一个节点,则调用evalNode()方法;如果表达式表示的是一组节点,则调用evalNodes()方法,源码如下:

        public List<XNode> evalNodes(String expression) {
            return evalNodes(document, expression);
        }
        
        public List<XNode> evalNodes(Object root, String expression) {
            List<XNode> xnodes = new ArrayList<XNode>();
            NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
            for (int i = 0; i < nodes.getLength(); i++) {
                xnodes.add(new XNode(this, nodes.item(i), variables));
            }
            return xnodes;
        }
        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;
            } else {
                return new XNode(this, node, variables);
            }
        }
    
    四、测试案例

      对于eval*()系列方法,在[MyBatis源码详解 - 解析器模块 - 组件一] XNode中的测试案例已经包含了该部分测试内容,此处不再赘叙,重点测试构造函数解析获取document属性的过程,使用简单的模式,不校验xml格式,测试用xml文件内容如下(从学习Mybatis时写的代码中搬过来的,具体配置忽略,注重构建document过程即可):

    <?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 resource="jdbc.properties"/>
        
        <!-- settings>
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings-->
        
        <settings>
            <setting name="logImpl" value="log4j" /> <!-- maven项目输出debug日志设置 -->
            <setting name="useActualParamName" value="true" />
        </settings>
        
        <typeAliases> 
            <package name="com.learn.ssm.chapter5.pojo"/>
        </typeAliases>  
        
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${database.driver}"/>
                    <property name="url" value="${database.url}"/>
                    <property name="username" value="${database.username}"/>
                    <property name="password" value="${database.password}"/>
                </dataSource>
            </environment>
        </environments> 
        
        <mappers>
            <!-- 注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。 -->
            <!-- package name="com.learn.ssm.chapter5.mapper"/-->
            <mapper resource="com/learn/ssm/chapter5/mapper/RoleMapper.xml"/>
        </mappers>  
    </configuration>
    

      测试案例代码如下,跑案例时在new XPathParser的地方打个断点,跟进入看看,就能知道构造XPathPserser的完整流程。

    public class XPathParserTest {
        public static void main(String[] args) {
            // 如果XML文件直接放在源目录(Source Directory)下,则可以直接解析
            // 如果放在源目录下的子目录中,则必须加上相对工程路径
            // InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("org/apache/ibatis/parsing/test/mybatis-config.xml");
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
            XPathParser xPathParser = new XPathParser(inputStream, false, null, null);
            System.out.println(xPathParser);
        }
    }
    
    五、我的github(注释源码、测试案例)

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

    相关文章

      网友评论

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

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