該系列文件是本人在學習 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初始化過程中,大致會有以下幾個步驟:
-
建立
Configuration
全域性配置物件,會往TypeAliasRegistry
別名註冊中心新增Mybatis需要用到的相關類,並設定預設的語言驅動類為XMLLanguageDriver
-
載入
mybatis-config.xml
配置檔案、Mapper介面中的註解資訊和XML對映檔案,解析後的配置資訊會形成相應的物件並儲存到Configuration全域性配置物件中 -
構建
DefaultSqlSessionFactory
物件,通過它可以建立DefaultSqlSession
物件,MyBatis中SqlSession
的預設實現類
因為整個初始化過程涉及到的程式碼比較多,所以拆分成了四個模組依次對MyBatis的初始化進行分析:
- 《MyBatis初始化(一)之載入mybatis-config.xml》
- 《MyBatis初始化(二)之載入Mapper介面與XML對映檔案》
- 《MyBatis初始化(三)之SQL初始化(上)》
- 《MyBatis初始化(四)之SQL初始化(下)》
由於在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
方法主要做了三件事:
- 建立
XMLConfigBuilder
物件,生成XPathParser
配置檔案解析器物件和Configuration
全域性配置物件 - 通過
XMLConfigBuilder
物件解析XML對映檔案,配置資訊、生成的相應物件都會儲存至Configuration
全域性配置物件中 - 構建一個
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;
}
-
首先會進入
XPathParser
的構造方法,將XML配置檔案解析成org.w3c.dom.Document
物件,這裡傳入了XMLMapperEntityResolver
作為解析例項物件,其中使用到MyBatis本地的DTD檔案 -
然後進入
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方法
- 解析
<properties />
標籤,成 Properties 物件。 - 覆蓋
configuration
中的 Properties 物件到上面的結果。 - 設定結果到
parser
和configuration
中。
settingsAsProperties方法
-
將
<setting />
標籤解析為 Properties 物件 -
接下來會根據該屬性物件載入使用者自定義的VFS實現類和Log實現類,並將配置資訊配置到Configuration全域性配置物件中
typeAliasesElement方法
- 對
<typeAliases />
的子標籤進行解析 - 如果是
<package />
字標籤,則對其配置的name包名進行解析,將別名去Class物件進行對映往TypeAliasRegistry註冊,呼叫TypeAliasRegistry.registerAliases
方法 - 否則就是配置的單個別名配置,進行解析並新增到TypeAliasRegistry中
pluginElement方法
- 對
<plugins />
的子標籤進行解析 - 生成對應的Interceptor攔截器物件並設定配置的屬性
- 將攔截器新增到 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 原始碼分析》