zoukankan      html  css  js  c++  java
  • MyBatis源码分析(1)-MapConfig文件的解析

    1.简述

       MyBatis是一个优秀的轻ORM框架,由最初的iBatis演化而来,可以方便的完成sql语句的输入输出到java对象之间的相互映射,典型的MyBatis使用的方式如下:

    String resource = "org/mybatis/example/mybatis-config.xml";
    
    InputStream inputStream = Resources.getResourceAsStream(resource);
    
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    SqlSession session = sqlSessionFactory.openSession();
    
    try {
    
      Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
    
    } finally {
    
      session.close();
    
    }

       这个mybatis-config.xml文件是MyBatis的入口,所有的数据源、全局性的配置、以及SqlMap文件的位置都在这个文件配置,以下是一个MapConfig文件的例子:

    <?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="org/apache/ibatis/builder/mapper.properties">
        <property name="driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
      </properties>
    
    
      <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="false"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="25"/>
      </settings>
    
    
      <typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
      </typeAliases>
    
    
      <typeHandlers>
        <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.ExampleTypeHandler"/>
      </typeHandlers>
    
    
      <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
        <property name="objectFactoryProperty" value="100"/>
      </objectFactory>
    
    
      <plugins>
        <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
          <property name="pluginProperty" value="100"/>
        </plugin>
      </plugins>
    
    
      <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC">
            <property name="" value=""/>
          </transactionManager>
          <dataSource type="UNPOOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
          </dataSource>
        </environment>
      </environments>
    
    
      <mappers>
        <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
      </mappers>
    
    
    </configuration>

    弄清楚MapConfig文件是如何解析的,以及各个配置项对应的作用,无疑对使用和理解MyBatis框架有着极大的好处。

    2. 三个类的介绍

      MapConfig文件的解析,我认为必须先介绍一下框架中的三个类,XNode,XPathParser和BaseBuilder。

      2.1 XNode

          XNode是MyBatis框架对于XML文件中Node类的扩展,它的成员变量有

    public class XNode {
    
    
    
      private Node node;   //org.w3c.dom.Node 表示的是xml中的一个节点
    
      private String name; //节点的名称
    
      private String body; //节点的文本内容
    
      private Properties attributes;   //全局性的配置变量,在新建XNode实例对象的时候传进来
    
      private Properties variables;    //节点本身的属性变量
    
      private XPathParser xpathParser; //节点本身拥有的XPathParser实例,这个XPathParser是一个基于
    
                                            //XPath机制解析XML文件的工具类
    
      .....
    
    }
    一个XNode对象是一个XML文件中节点的抽象,它提供了一系列公共方法,使得框架的其他的程序能够很方便的访问节点的某个属性,例如这个方法:
    public XNode evalNode(String expression) {
    
        return xpathParser.evalNode(node, expression);
    
      }

       evalNode能够更具XPath表达式获取一个节点下的任意一个节点,实际上调用的是XNode实例自身持有的XPathParser实例对象的方法。

       2.2 XPathParser

       XPathParser是一个工具类,

    public class XPathParser {
    
    
    
      private Document document;               //持有的Document对象
    
      private boolean validation;              //是否进行DTD验证
    
      private EntityResolver entityResolver;   //DTD验证的接口
    
      private Properties variables;            //全局性的配置变量
    
      private XPath xpath;                     //XPath接口
    
      ...
    
    }

    对于XNode中的evalNode方法,实际上调用的是XPathParser中的evalNode方法,代码片段如下:

      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);
    
        }
    
      }
    
    可见,通过层层的调用,最后还是调用的Xpath中的evaluate方法,其中第三个参数 XPathConstants.NODE 指明了该方法返回的是一个Node对象,第一个参数是XPath的语法表达式,XPath的教程可以参考这里
    2.3 BaseBuilder
    BaseBuilder是所有MyBatis框架中所有XML文件解析器的基类,之后的XMLConfigBuilder、XMLMapperBuilder、MapperBuilderAssistant都是继承了此类,此类持有的属性有:
    public abstract class BaseBuilder {
    
      protected final Configuration configuration;              //一切的配置信息都会汇总到这个类中
    
      protected final TypeAliasRegistry typeAliasRegistry;      //用于别名的解析
    
      protected final TypeHandlerRegistry typeHandlerRegistry;  //用于java类型的解析
      }

    其中的Configuration类,就是解析XML配置文件,SqlMap配置文件的归宿。

    3. XMLConfigBuilder完成的工作

    完成SqlConfig解析工作主要是XMLConfigBuilder它继承自BaseBuilder,它对外提供了很多个不同签名的公共构造函数,以满足不同场合的需要,但是所有的这些对外的公共构造函数,最终还是调用的其私有的构造函数:

      private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    
        super(new Configuration());
    
        ErrorContext.instance().resource("SQL Mapper Configuration");
    
        this.configuration.setVariables(props);
    
        this.parsed = false;
    
        this.environment = environment;
    
        this.parser = parser;
    
      }
    首先,调用基类的构造函数,传入一个新的Configuration实例。第二个参数environment,指定的是XMLconfig文件中的environments下environment节点名称,在一个配置文件中,可以配置多个environment节点,这样对应的是不同的环境配置信息,例如你可以配置两个environment节点,一个对应生产,一个对应开发。如果这个参数为空,则用id为default的environment节点对应的环境信息。
    建立了XMLConfigBuilder实例之后,主要调用下面的方法,完成所有配置文件的解析:
      private void parseConfiguration(XNode root) {
    
        try {
    
          propertiesElement(root.evalNode("properties")); //issue #117 read properties first
    
          typeAliasesElement(root.evalNode("typeAliases"));
    
          pluginElement(root.evalNode("plugins"));
    
          objectFactoryElement(root.evalNode("objectFactory"));
    
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    
          settingsElement(root.evalNode("settings"));
    
          environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
    
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    
          typeHandlerElement(root.evalNode("typeHandlers"));
    
          mapperElement(root.evalNode("mappers"));
    
        } catch (Exception e) {
    
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    
        }
    
      }
    
    该方法对应的root节点是SqlConfig文件的根节点,调用root.evelNode(String nodeName)之后,返回的是SqlConfig文件根节点下的某一个节点,之后针对不同的节点,调用不同的函数,进行处理。
    propertiesElement(root.evalNode("properties"));
    其中root.evalNode(“properties”)实际上返回的是SqlConfig文件下的节点,对应的XML文件片段为
      <properties resource="org/apache/ibatis/builder/mapper.properties">
    
        <property name="driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
    
      </properties>

    这个节点设置了一个配置文件,仔细来看看拿到这个节点的XNode实例之后,propertiesElement做了一些什么:

      private void propertiesElement(XNode context) throws Exception {
    
        if (context != null) {
    
          //将节点下的子节点转换成Properties实例,其中键名由节点的name决定,键值由节点的value指定
    
          Properties defaults = context.getChildrenAsProperties(); 
    
          //获取节点属性resouce的值,对应一个配置文件的地址
    
          String resource = context.getStringAttribute("resource");
    
          //获取节点属性url的值,对应一个配置文件的地址
    
          String url = context.getStringAttribute("url");
    
          if (resource != null && url != null) {
    
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
    
          }
    
          if (resource != null) {
    
            //将resource指定的配置文件的转换成Properties实例,并且和之前的生成的Properties实例合并
    
            defaults.putAll(Resources.getResourceAsProperties(resource));
    
          } else if (url != null) {
    
            defaults.putAll(Resources.getUrlAsProperties(url));
    
          }
    
          //将configuration中已经有的Properties实例读取出来,和之前的生成的Properties实例合并
    
          Properties vars = configuration.getVariables();
    
          if (vars != null) {
    
            defaults.putAll(vars);
    
          }
    
          //将新生成的Properties实例设置回XpathParser和Configuration实例。
    
          parser.setVariables(defaults);
    
          configuration.setVariables(defaults);
    
        }
    
      }

             可见,经过这么一段处理以后,properties节点下对应的配置文件,和单独的以子节点形式设置的配置信息,都以Properties实例的形式存储到了最终要生成的Configuration实例中,这个Properties的实例也会贯彻之后的解析过程,供其他的方法使用。至此算是完成了Properties节点的解析。
             之后的settings、typeAliases、typeHandlers等节点的解析类似,基本上都是放到不同的私有方法中来完成的,只不过由于解析对象的不同,这些方法的复杂度也不一样。这样的话,当新增一种的新的节点,只需要新增一种新的私有方法。

    4. 小结
             之前看过iBatis的源码,iBatis对于SqlConfig文件的解析核心是利用了私有类的回调函数来解析的,结合《iBATIS框架源码剖析》我还花了一整天的时间搞明白,因为里面的代码实在太复杂。MyBatis对于XML文件的解析部分全部重新了,核心是对于XML节点的抽象XNode以及对于XPath二度进行包装的工具类XPathParser,结合GOF中的建造者模式,做到了较清晰的代码结构和逻辑抽象,使得代码更加容易让人理解。

    PS:MyBatis的源码分析是我14年上半年的重要工作,这个系列的博客尽量会保持两周一篇的速度。

  • 相关阅读:
    Pandas 学习记录(一)
    python 列表常用操作
    pandas 基本操作
    Numpy np.array 相关常用操作学习笔记
    JS控制背景音乐 没有界面
    Linux Awk使用案例总结
    Yii2 定时任务创建(Console 任务)
    YII2项目常用技能知识总结
    /etc/fstab readyonly 解决办法
    Redis 排行榜 自己简单练习
  • 原文地址:https://www.cnblogs.com/javanerd/p/3537693.html
Copyright © 2011-2022 走看看