精盡 MyBatis 原始碼分析 - MyBatis 初始化(一)之載入 mybatis-config.xml
阿新 • • 發佈:2020-11-23
> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring)、[Spring-Boot-Starter 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-boot-starter))進行閱讀
>
> 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初始化(一)之載入mybatis-config.xml》**](https://www.cnblogs.com/lifullmoon/p/14015009.html)
- **《MyBatis初始化(二)之載入Mapper介面與XML對映檔案》**
- **《MyBatis初始化(三)之SQL初始化(上)》**
- **《MyBatis初始化(四)之SQL初始化(下)》**
由於在MyBatis的初始化過程中去解析Mapper介面與XML對映檔案涉及到的篇幅比較多,XML對映檔案的解析過程也比較複雜,所以才分成了後面三個模組,逐步分析,這樣便於理解
## 初始化(一)之載入mybatis-config.xml
本文主要分享的是在MyBatis初始化過程中,是如何載入`mybatis-config.xml`配置檔案的,配置描述請參考:[MyBatis官方文件的配置說明](https://mybatis.org/mybatis-3/zh/configuration.html)
初始化入口在`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`方法:
```java
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全域性配置物件
#### 構造方法
```java
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方法
```java
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> 解析 標籤
propertiesElement(root.evalNode("properties"));
// <2> 解析 標籤,解析配置生成 Properties 物件
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 根據配置載入自定義 VFS 實現類
loadCustomVfs(settings);
// 根據配置載入自定義的 Log 實現類
loadCustomLogImpl(settings);
// <3> 解析 標籤,生成別名與類的對映關係
typeAliasesElement(root.evalNode("typeAliases"));
// <4> 解析 標籤,新增自定義攔截器外掛
pluginElement(root.evalNode("plugins"));
// <5> 解析 標籤,自定義例項工廠
objectFactoryElement(root.evalNode("objectFactory"));
// <6> 解析 標籤,自定義 ObjectWrapperFactory 工廠,無預設實現
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// <7> 解析 標籤,自定義 Reflector 工廠
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 將 配置資訊新增到 Configuration 屬性
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// <8> 解析 標籤,自定義當前環境資訊
environmentsElement(root.evalNode("environments"));
// <9> 解析 標籤,資料庫識別符號
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// <10> 解析 標籤,自定義型別處理器
typeHandlerElement(root.evalNode("typeHandlers"));
// <11> 解析 標籤,掃描Mapper介面並進行解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
```
在`parse()`解析方法中,獲取到Document物件的` `節點,然後呼叫`parseConfiguration`進行解析,依次解析以下標籤:
`<1> `解析` `標籤,呼叫`propertiesElement`方法
`<2>`解析` `標籤,解析配置生成 Properties 物件,呼叫`settingsAsProperties`方法
`<3> `解析` `標籤,生成別名與類的對映關係,呼叫`typeAliasesElement`方法
`<4> `解析` `標籤,新增自定義攔截器外掛,呼叫`pluginElement`方法
`<5> `解析` `標籤,自定義例項工廠,呼叫`objectFactoryElement`方法
`<6>`解析` `標籤,自定義 ObjectWrapperFactory 工廠,呼叫`objectWrapperFactoryElement`方法
`<7> `解析` `標籤,自定義 Reflector 工廠,呼叫`reflectorFactoryElement`方法
`<8> `解析` `標籤,自定義當前環境資訊,呼叫`environmentsElement`方法
`<9>`解析` `標籤,資料庫識別符號,呼叫`databaseIdProviderElement`方法
`<10>`解析` `標籤,自定義型別處理器,呼叫`typeHandlerElement`方法
`<11> `解析` `標籤,掃描Mapper介面並進行解析,呼叫`mapperElement`方法
關於MyBatis的配置描述請參考[MyBatis官方文件的配置說明](https://mybatis.org/mybatis-3/zh/configuration.html)
上面涉及到的解析方法就不一一列出來了,這裡做個簡單的描述,具體請閱讀這個類