Mybatis源碼解析,一步一步從淺入深(四):將configuration.xml的解析到Configuration對象實例

Mybatis源碼解析,一步一步從淺入深(二):按步驟解析源碼中咱們看到了XMLConfigBuilder(xml配置解析器)的實例化。並且這個實例化過程在文章:Mybatis源碼解析,一步一步從淺入深(三):實例化xml配置解析器(XMLConfigBuilder)也進行了詳細的闡述。html

那麼接下來就是解析configuration.xml並將configuration.xml中的配置信息加載到Configuration實例對象中去。mysql

一,先來看看代碼的位置sql

  

  在文章:Mybatis源碼解析,一步一步從淺入深(二):按步驟解析源碼中提到了parser.parse()方法會返回要給Configuration對象實例,並且在另一篇文章Mybatis源碼解析,一步一步從淺入深(三):實例化xml配置解析器(XMLConfigBuilder)中也詳細闡述了XMLConfigBuilder和Configuration 這兩個類的實例化過程。接下來就研究一下parser.parse()方法的執行過程。數據庫

  不過要千萬注意 這裏的parser是XMLConfigBuilder對象實例。而XMLConfigBuilder中屬性parser 是XPathParser對象實例。mybatis

二,廢話很少說,直接看XMLConfigBuilder類的parse()方法源碼:app

  

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

  能夠清晰的看到,這個方法返回的是一個Configuration對象實例。ide

  這個方法首先會判斷parsed屬性的值,還記得在文章Mybatis源碼解析,一步一步從淺入深(三):實例化xml配置解析器(XMLConfigBuilder)中提到的XMLConfigBuilder初始化過程嗎?在調用XMLConfigBuilder的構造方法中,parsed賦的值是false,即表示沒有進行解析。這個方法中首先會校驗parsed的真假,若是爲真就拋出BuilderException異常,並反饋信息說:"Each XMLConfigBuilder can only be used once.",每個XMLConfigBuilder只能被解析一次。post

  接着就將parsed 賦值爲ture。ui

  可是真正的解析工做尚未開始,你猜的沒錯,真正的解析工做是在parseConfiguration方法中完成的,那讓咱們趕忙看看下parserConfiguration方法的詳細信息吧。this

三,開始解析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節點的解析


 原創不易,轉載請註明出處:http://www.javashuo.com/article/p-rqohjjxr-n.html 

相關文章
相關標籤/搜索