精盡 MyBatis 原始碼分析 - MyBatis 初始化(一)之載入 mybatis-config.xml

月圓吖發表於2020-11-23

該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋(Mybatis原始碼分析 GitHub 地址Mybatis-Spring 原始碼分析 GitHub 地址Spring-Boot-Starter 原始碼分析 GitHub 地址)進行閱讀

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的初始化

在MyBatis初始化過程中,大致會有以下幾個步驟:

  1. 建立Configuration全域性配置物件,會往TypeAliasRegistry別名註冊中心新增Mybatis需要用到的相關類,並設定預設的語言驅動類為XMLLanguageDriver

  2. 載入mybatis-config.xml配置檔案、Mapper介面中的註解資訊和XML對映檔案,解析後的配置資訊會形成相應的物件並儲存到Configuration全域性配置物件中

  3. 構建DefaultSqlSessionFactory物件,通過它可以建立DefaultSqlSession物件,MyBatis中SqlSession的預設實現類

因為整個初始化過程涉及到的程式碼比較多,所以拆分成了四個模組依次對MyBatis的初始化進行分析:

由於在MyBatis的初始化過程中去解析Mapper介面與XML對映檔案涉及到的篇幅比較多,XML對映檔案的解析過程也比較複雜,所以才分成了後面三個模組,逐步分析,這樣便於理解

初始化(一)之載入mybatis-config.xml

本文主要分享的是在MyBatis初始化過程中,是如何載入mybatis-config.xml配置檔案的,配置描述請參考:MyBatis官方文件的配置說明

初始化入口在org.apache.ibatis.session.SqlSessionFactoryBuilder構造器中,因為需要通過mybatis-config.xml配置檔案構建一個SqlSessionFactory工廠,用於建立SqlSession會話

主要涉及到以下幾個類:

  • org.apache.ibatis.session.SqlSessionFactoryBuilder:用於構建SqlSessionFactory工廠
  • org.apache.ibatis.builder.xml.XMLConfigBuilder:根據配置檔案進行解析,開始Mapper介面與XML對映檔案的初始化,生成Configuration全域性配置物件
  • org.apache.ibatis.builder.xml.XMLMapperBuilder:繼承BaseBuilder抽象類,用於解析XML對映檔案內的標籤
  • org.apache.ibatis.session.Configuration:MyBatis的全域性配置物件,儲存所有的配置與初始化過程所產生的物件

SqlSessionFactoryBuilder

org.apache.ibatis.session.SqlSessionFactoryBuilder:構建SqlSessionFactory工廠類,裡面定義了許多build過載方法,主要分為處理Reader和InputStream兩種檔案資源物件

我們來看看其中的一個build方法:

public class SqlSessionFactoryBuilder {

	public SqlSessionFactory build(Reader reader) {
		return build(reader, null, null);
	}

	/**
	 * 構造 SqlSessionFactory 物件
	 *
	 * @param reader Reader 物件
	 * @param environment 環境
	 * @param properties Properties 變數
	 * @return SqlSessionFactory 物件
	 */
	public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
		try {
			/*
			 * <1> 建立 XMLConfigBuilder 物件
			 * 會生成一個 XPathParser,包含 Document 物件
			 * 會建立一個 Configuration 全域性配置物件
			 */
			XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
			/*
			 * <2> 解析 XML 檔案並配置到 Configuration 全域性配置物件中
			 * <3> 建立 DefaultSqlSessionFactory 物件
			 */
			return build(parser.parse());
		} catch (Exception e) {
			throw ExceptionFactory.wrapException("Error building SqlSession.", e);
		} finally {
			ErrorContext.instance().reset();
			try {
				reader.close();
			} catch (IOException e) {
				// Intentionally ignore. Prefer previous error.
			}
		}
	}
}

build方法主要做了三件事:

  1. 建立XMLConfigBuilder物件,生成XPathParser配置檔案解析器物件和Configuration全域性配置物件
  2. 通過XMLConfigBuilder物件解析XML對映檔案,配置資訊、生成的相應物件都會儲存至Configuration全域性配置物件中
  3. 構建一個DefaultSqlSessionFactory物件

XMLConfigBuilder

org.apache.ibatis.builder.xml.XMLConfigBuilder:根據配置檔案進行解析,開始Mapper介面與XML對映檔案的初始化,生成Configuration全域性配置物件

構造方法

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // <1> 建立 Configuration 物件
    super(new Configuration());
    // 建立一個當前執行緒的上下文,記錄錯誤資訊
    ErrorContext.instance().resource("SQL Mapper Configuration");
    // <2> 設定 Configuration 的 variables 屬性
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}
  1. 首先會進入XPathParser的構造方法,將XML配置檔案解析成org.w3c.dom.Document物件,這裡傳入了XMLMapperEntityResolver作為解析例項物件,其中使用到MyBatis本地的DTD檔案

  2. 然後進入XMLConfigBuilder的另一個構造方法,會先建立一個Configuration全域性配置物件,初始化一些物件

parse方法

public Configuration parse() {
    // <1.1> 若已解析,丟擲 BuilderException 異常
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // <1.2> 標記已解析
    parsed = true;
    // <2> 解析 XML configuration 節點
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
private void parseConfiguration(XNode root) {
    try {
        // issue #117 read properties first
        // <1> 解析 <properties /> 標籤
        propertiesElement(root.evalNode("properties"));
        // <2> 解析 <settings /> 標籤,解析配置生成 Properties 物件
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        // 根據配置載入自定義 VFS 實現類
        loadCustomVfs(settings);
        // 根據配置載入自定義的 Log 實現類
        loadCustomLogImpl(settings);
        // <3> 解析 <typeAliases /> 標籤,生成別名與類的對映關係
        typeAliasesElement(root.evalNode("typeAliases"));
        // <4> 解析 <plugins /> 標籤,新增自定義攔截器外掛
        pluginElement(root.evalNode("plugins"));
        // <5> 解析 <objectFactory /> 標籤,自定義例項工廠
        objectFactoryElement(root.evalNode("objectFactory"));
        // <6> 解析 <objectWrapperFactory /> 標籤,自定義 ObjectWrapperFactory 工廠,無預設實現
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // <7> 解析 <reflectorFactory /> 標籤,自定義 Reflector 工廠
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // 將 <settings /> 配置資訊新增到 Configuration 屬性
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        // <8> 解析 <environments /> 標籤,自定義當前環境資訊
        environmentsElement(root.evalNode("environments"));
        // <9> 解析 <databaseIdProvider /> 標籤,資料庫識別符號
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // <10> 解析 <typeHandlers /> 標籤,自定義型別處理器
        typeHandlerElement(root.evalNode("typeHandlers"));
        // <11> 解析 <mappers /> 標籤,掃描Mapper介面並進行解析
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

parse()解析方法中,獲取到Document物件的<configuration />節點,然後呼叫parseConfiguration進行解析,依次解析以下標籤:

<1> 解析<properties /> 標籤,呼叫propertiesElement方法

<2>解析<settings />標籤,解析配置生成 Properties 物件,呼叫settingsAsProperties方法

<3> 解析<typeAliases />標籤,生成別名與類的對映關係,呼叫typeAliasesElement方法

<4> 解析<plugins />標籤,新增自定義攔截器外掛,呼叫pluginElement方法

<5> 解析<objectFactory />標籤,自定義例項工廠,呼叫objectFactoryElement方法

<6>解析<objectWrapperFactory /> 標籤,自定義 ObjectWrapperFactory 工廠,呼叫objectWrapperFactoryElement方法

<7> 解析<reflectorFactory />標籤,自定義 Reflector 工廠,呼叫reflectorFactoryElement方法

<8> 解析<environments />標籤,自定義當前環境資訊,呼叫environmentsElement方法

<9>解析<databaseIdProvider />標籤,資料庫識別符號,呼叫databaseIdProviderElement方法

<10>解析<typeHandlers />標籤,自定義型別處理器,呼叫typeHandlerElement方法

<11> 解析<mappers />標籤,掃描Mapper介面並進行解析,呼叫mapperElement方法

關於MyBatis的配置描述請參考MyBatis官方文件的配置說明

上面涉及到的解析方法就不一一列出來了,這裡做個簡單的描述,具體請閱讀這個類???,我已經做好了註釋

propertiesElement方法

  1. 解析 <properties /> 標籤,成 Properties 物件。
  2. 覆蓋 configuration 中的 Properties 物件到上面的結果。
  3. 設定結果到 parserconfiguration 中。

settingsAsProperties方法

  1. <setting /> 標籤解析為 Properties 物件

  2. 接下來會根據該屬性物件載入使用者自定義的VFS實現類和Log實現類,並將配置資訊配置到Configuration全域性配置物件中

typeAliasesElement方法

  1. <typeAliases /> 的子標籤進行解析
  2. 如果是<package />字標籤,則對其配置的name包名進行解析,將別名去Class物件進行對映往TypeAliasRegistry註冊,呼叫TypeAliasRegistry.registerAliases方法
  3. 否則就是配置的單個別名配置,進行解析並新增到TypeAliasRegistry中

pluginElement方法

  1. <plugins /> 的子標籤進行解析
  2. 生成對應的Interceptor攔截器物件並設定配置的屬性
  3. 將攔截器新增到 Configuration 全域性配置的 InterceptorChain 攔截器呼叫鏈中,後續會講到,例如com.github.pagehelper.PageInterceptor分頁外掛

mapperElement方法

在該方法中會解析Mapper介面,建立對應的MapperProxyFactory動態代理工廠,同時也會解析Mapper介面對應的XML對映檔案

整個的解析過程涉及到的程式碼有點多,在下一個模組《MyBatis初始化(二)之載入Mapper介面與XML對映檔案》中進行分析

Configuration

org.apache.ibatis.session.Configuration:MyBatis的全域性配置物件,儲存所有的配置與初始化過程所產生的物件,內容有點多,這裡就不列出來了,參考:Configuration.java

總結

MyBatis會在SqlSessionFactoryBuilder構造器中根據mybatis-config.xml配置檔案初始化Configuration全域性配置物件,然後建立對應的DefaultSqlSessionFactory工廠

在解析配置檔案的過程中,MyBatis的配置都會儲存在Configuration物件中,對Mapper介面和XML對映檔案進行解析生成相應的物件都會儲存在其中

參考文章:芋道原始碼《精盡 MyBatis 原始碼分析》

相關文章