精盡 MyBatis 源碼分析 – MyBatis 初始化(一)之載入 mybatis-config.xml
- 2020 年 11 月 23 日
- 筆記
- mybatis, 源碼解析, 精盡MyBatis源碼分析
該系列文檔是本人在學習 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 源碼分析》