• Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例


    Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化。而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)也进行了详细的阐述。

    那么接下来就是解析configuration.xml并将configuration.xml中的配置信息加载到Configuration实例对象中去。

    一,先来看看代码的位置

      

      在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中提到了parser.parse()方法会返回要给Configuration对象实例,而且在另外一篇文章Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)中也详细阐述了XMLConfigBuilder和Configuration 这两个类的实例化过程。接下来就研究一下parser.parse()方法的执行过程。

      不过要千万注意 这里的parser是XMLConfigBuilder对象实例。而XMLConfigBuilder中属性parser 是XPathParser对象实例。

    二,废话不多说,直接看XMLConfigBuilder类的parse()方法源码:

      

      public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }

      可以清晰的看到,这个方法返回的是一个Configuration对象实例。

      这个方法首先会判断parsed属性的值,还记得在文章Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)中提到的XMLConfigBuilder初始化过程吗?在调用XMLConfigBuilder的构造方法中,parsed赋的值是false,即表示没有进行解析。这个方法中首先会校验parsed的真假,如果为真就抛出BuilderException异常,并反馈信息说:"Each XMLConfigBuilder can only be used once.",每一个XMLConfigBuilder只能被解析一次。

      接着就将parsed 赋值为ture。

      但是真正的解析工作还没有开始,你猜的没错,真正的解析工作是在parseConfiguration方法中完成的,那让我们赶紧看看下parserConfiguration方法的详细信息吧。

    三,开始解析configuration.xml

      

    private void parseConfiguration(XNode root) {
        try {
          //解析<properties resource="dbConfig.properties"></properties>节点
          propertiesElement(root.evalNode("properties")); //issue #117 read properties first
          //解析typeAliases节点
          typeAliasesElement(root.evalNode("typeAliases"));
          //解析plugins节点
          pluginElement(root.evalNode("plugins"));
          //解析objectFactory节点
          objectFactoryElement(root.evalNode("objectFactory"));
          //解析objectWrapperFactory节点
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          //解析<settings></settings>节点
          settingsElement(root.evalNode("settings"));
          //解析environments节点
          environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
          //解析databaseIdProvider节点
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          //解析typeHandlers节点
          typeHandlerElement(root.evalNode("typeHandlers"));
          //解析mappers节点
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }

      在这个parseConfiguration方法中,将不同节点的解析工作又交给了不同的方法完成。说了这么多话,看了这么多代码,终于开始解析mybatis的核心配置文件configuration.xml文件了,首先回顾一下configuration.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>
    
      <!-- 指定properties配置文件, 里面配置的是数据库相关 -->
      <properties resource="dbConfig.properties"></properties>
      
      <!-- 指定Mybatis使用log4j -->
      <settings>
         <setting name="logImpl" value="LOG4J"/>
      </settings>
          
      <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
          <dataSource type="POOLED">
             <!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
             <property name="driver" value="${driver}"/>
             <property name="url" value="${url}"/>
             <property name="username" value="${username}"/>
             <property name="password" value="${password}"/>         
          </dataSource>
        </environment>
      </environments>
      
      <!-- 映射文件,mybatis精髓 -->
      <mappers>
        <mapper resource="mapper/userDao-mapping.xml"/>
      </mappers>
      
    </configuration>

      从configuration.xml文件中我们看到,在根节点configuration中存在四个子节点,分别是:properties,settings,environments,mappers。那么对应到parserConfiguration方法中,本工程用到的解析方法就是:

      1,

        //解析<properties resource="dbConfig.properties"></properties>节点
        propertiesElement(root.evalNode("properties"));

      2,

        //解析<settings></settings>节点
        settingsElement(root.evalNode("settings"));

      3,  

        //解析environments节点
        environmentsElement(root.evalNode("environments"));

       4,

        //解析mappers节点
        mapperElement(root.evalNode("mappers"));

      我们就按照这四个方法逐一的去跟踪

    四,解析properties节点

      首先先看一下configuration.xml中properties节点的内容

        

    <!-- 指定properties配置文件, 里面配置的是数据库相关 -->
      <properties resource="dbConfig.properties"></properties>

      ok ,这里主要是引入了一个配置文件,配置文件中配置的是数据库信息,顺便也看一下dbConfig.properties的内容吧

      

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/learnMybatis
    username=root
    password=123

      数据库的常规配置,没有什么特殊的需要解释的。好的,一起来探一探解析properties节点的方法propertiesElement的究竟把。

      

      private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
        // 以Properties的形式,获取context(就是<properties></properties>)的子节点,这里为空 Properties defaults
    = context.getChildrenAsProperties();
        // 获取resource属性, resource = "dbConfig.properties" String resource
    = context.getStringAttribute("resource");
        // 获取url属性,没有url属性,故 url = null; 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) {
          //加载dbConfig.properties中的数据库配置信息到defaults中 defaults.putAll(Resources.getResourceAsProperties(resource)); }
    else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); }
        //获取已经存在的配置 Properties vars
    = configuration.getVariables(); if (vars != null) {
          //合并配置 defaults.putAll(vars); }
        //保存配置 parser.setVariables(defaults);
        //保存配置 configuration.setVariables(defaults); } }

      很明了,就是解析出dbConfig.properties文件名,并读取dbConfig.properties文件的内容,放入defaults中。最后把defaults放入parser.variables和configuration.variables中。注意这里的parser可不是本篇文章开头说的XMLConfigBuilder对象哦,这个parser是XPathParser对象。不清楚可以去Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)中看一看。简答的说这一步就是把数据库的配置信息加载进来了。

      但是从源码中还可以得到另外一点,就是properties节点可以用resource属性从本地classpath加载配置,也可以使用url从网路资源加载配置。但是两种方式不能同时存在,否则就报错BuilderException:The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.

    五,解析settings节点

      先看configuration.xml中settings节点的内容:

      

    <!-- 指定Mybatis使用log4j -->
      <settings>
         <setting name="logImpl" value="LOG4J"/>
      </settings>

      只是简单的配置了日志使用log4j。

      settingsElement方法详情:

      

    private void settingsElement(XNode context) throws Exception {
        if (context != null) {
          Properties props = context.getChildrenAsProperties();
          // 检查是不是所有的配置已经在Configuration中声明。
          MetaClass metaConfig = MetaClass.forClass(Configuration.class);
          for (Object key : props.keySet()) {
            if (!metaConfig.hasSetter(String.valueOf(key))) {
              throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
            }
          }
          ......
          configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
          ......
        }
      }

       首先检查name属性是不是在Configuration类中进行了声明,方法中使用的反射技术进行的检查,具体细节就不再赘述。

       当然logImpl 是Configuration中一个属性:

        

    public class Configuration {
      ......
      protected Class <? extends Log> logImpl;
      ......
    }

      接下来就是执行剩余的一行代码了,当然还有其他的代码应为用不到被我隐藏了。

      代码:configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));

      我们逐步解析一下这行代码:

      1,props.getProperty("logImpl") 返回的是:LOG4J

      2,resolveClass是从类型别名注册表(TypeAliasRegistry)中获取类对象,还记得类型别名注册表(TypeAliasRegistry)吗?如果不记得就去文章:Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)中看一下吧。那获取到的值也就是类对象是什么呢?相信你已经看到了,在Configuration的空参构造方法中有这么一行代码:typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);是的resolveClass("LOG4J")的返回值就是Log4jImpl.class

      3,configuration.setLogImpl(Log4jImpl.class)就是应用settings节点中配置的日志,源码如下:

        

     @SuppressWarnings("unchecked")
      public void setLogImpl(Class<?> logImpl) {
        if (logImpl != null) {
          this.logImpl = (Class<? extends Log>) logImpl;
          LogFactory.useCustomLogging(this.logImpl);
        }
      }

      

       到这里settins节点的解析工作就完成了,当然settings节点中还有其他类型的配置,接下来就剩下两个节点了:environments节点和mappers节点。先看看解析environments节点.

    六,解析environments节点

      老规矩先看看configuration.xml文件中environments节点的内容:

        

    <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
          <dataSource type="POOLED">
             <!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
             <property name="driver" value="${driver}"/>
             <property name="url" value="${url}"/>
             <property name="username" value="${username}"/>
             <property name="password" value="${password}"/>         
          </dataSource>
        </environment>
      </environments>

      可以看到这里主要是对数据库信息进行的配置。

      解析environments节点的environmentsElement方法的详情:

        

    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
          if (environment == null) {
            //获取默认指定的运行环境的id,也就是获取<environments default="development">中的development,即environment = "development"
            environment = context.getStringAttribute("default");
          }
          //遍历environments的子节点environment
          for (XNode child : context.getChildren()) {
            // 获取environment的id
            String id = child.getStringAttribute("id");
            //判断当前节点的id是否与指定的运行环境的id相等。
            if (isSpecifiedEnvironment(id)) {
              //实例化事务工厂
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
              //实例化连接池工厂
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
              //获取连接池
              DataSource dataSource = dsFactory.getDataSource();
              //根据运行环境id构建运行环境Environment实例
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());
            }
          }
        }
      }

      经过这个方法有关数据库相关的连接池等就已将初始化好了。并且将environmentBuilder.build()创建好的Environment对象实例赋值给Configuration对象实例的environment属性。

    七,解析mappers节点

      以上以及节点的解析只是在初始化运行环境,包括数据库环境,日志环境等,并且这些配置已经设定就轻易不会改变。但是mapper节点的解析才是所有解析中的重中之重。所以我打算另写一篇文章专门描述mapper节点的解析:Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析


     原创不易,转载请注明出处:https://www.cnblogs.com/zhangchengzi/p/9674527.html 

  • 相关阅读:
    算法-动态规划 Dynamic Programming--从菜鸟到老鸟
    DTW动态时间规整
    安装splash
    安装 Tesserocr (填坑)
    pip3 install tesserocr安装失败(已解决)
    从头到尾彻底理解傅里叶变换算法
    ruby之——安装gem提示:Please update your PATH to include build tools or download the DevKit
    关于0x80000000为什么等于-2147483648和负数在内存上储存的问题
    html5 canvas
    html5 视频和音频
  • 原文地址:https://www.cnblogs.com/zhangchengzi/p/9674527.html
Copyright © 2020-2023  润新知