精盡 MyBatis 原始碼分析 - 基礎支援層
阿新 • • 發佈:2020-11-22
> 該系列文件是本人在學習 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 原始碼分析 - 整體架構》](https://www.cnblogs.com/lifullmoon/p/14014901.html)中對 MyBatis 的基礎支援層已做過介紹,包含整個 MyBatis 的基礎模組,為核心處理層的功能提供了良好的支撐,本文對基礎支援層的每個模組進行分析
- **解析器模組**
- **反射模組**
- 異常模組
- **資料來源模組**
- 事務模組
- 快取模組
- **型別模組**
- **IO模組**
- 日誌模組
- 註解模組
- **Binding模組**
### 解析器模組
主要包路徑:org.apache.ibatis.parsing
主要功能:初始化時解析mybatis-config.xml配置檔案、為處理動態SQL語句中佔位符提供支援
主要檢視以下幾個類:
- `org.apache.ibatis.parsing.XPathParser`:基於Java **XPath** 解析器,用於解析MyBatis的mybatis-config.xml和**Mapper.xml等XML配置檔案
- `org.apache.ibatis.parsing.GenericTokenParser`:通用的Token解析器
- `org.apache.ibatis.parsing.PropertyParser`:動態屬性解析器
#### XPathParser
`org.apache.ibatis.parsing.XPathParser`:基於Java **XPath** 解析器,用於解析MyBatis的mybatis-config.xml和**Mapper.xml等XML配置檔案
主要程式碼如下:
```java
public class XPathParser {
/**
* XML Document 物件
*/
private final Document document;
/**
* 是否檢驗
*/
private boolean validation;
/**
* XML實體解析器
*/
private EntityResolver entityResolver;
/**
* 變數物件
*/
private Properties variables;
/**
* Java XPath 物件
*/
private XPath xpath;
public XPathParser(String xml) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public String evalString(String expression) {
return evalString(document, expression);
}
public String evalString(Object root, String expression) {
// <1> 獲得值
String result = (String) evaluate(expression, root, XPathConstants.STRING);
// <2> 基於 variables 替換動態值,如果 result 為動態值
result = PropertyParser.parse(result, variables);
return result;
}
private Object evaluate(String expression, Object root, QName returnType) {
try {
// 通過XPath結合表示式獲取Document物件中的結果
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
// <1> 獲得 Node 物件
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
// <2> 封裝成 XNode 物件
return new XNode(this, node, variables);
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 1> 建立 DocumentBuilderFactory 物件
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
// 2> 建立 DocumentBuilder 物件
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver); // 設定實體解析器
builder.setErrorHandler(new ErrorHandler() { // 設定異常處理,實現都空的
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
// NOP
}
});
// 3> 解析 XML 檔案,將檔案載入到Document中
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
}
```
看到定義的幾個屬性:
| 型別 | 屬性名 | 說明 |
| -------------- | -------------- | ------------------------------------------------------------ |
| Document | document | XML檔案被解析後生成對應的`org.w3c.dom.Document`物件 |
| boolean | validation | 是否校驗XML檔案,一般情況下為true |
| EntityResolver | entityResolver | `org.xml.sax.EntityResolver`物件,XML實體解析器,一般通過自定義的`org.apache.ibatis.builder.xml.XMLMapperEntityResolver`從本地獲取DTD檔案解析 |
| Properties | variables | 變數Properties物件,用來替換需要動態配置的屬性值,例如我們在MyBatis的配置檔案中使用變數將使用者名稱密碼放在另外一個配置檔案中,那麼這個配置會被解析到Properties物件用,用於替換XML檔案中的動態值 |
| XPath | xpath | `javax.xml.xpath.XPath` 物件,用於查詢XML中的節點和元素 |
建構函式有很多,基本都相似,內部都是呼叫`commonConstructor`方法設定相關屬性和`createDocument`方法為該XML檔案建立一個Document物件
提供了一系列的`eval*`方法,用於獲取Document物件中的元素或者節點:
- eval*元素的方法:根據表示式獲取我們常用型別的元素的值,其中會基於`variables`呼叫`PropertyParser`的`parse`方法替換掉其中的動態值(如果存在),這就是MyBatis如何替換掉XML中的動態值實現的方式
- eval*節點的方法:根據表示式獲取到`org.w3c.dom.Node`節點物件,將其封裝成自己定義的`XNode`物件,方便主要為了**動態值的替換**
#### PropertyParser
`org.apache.ibatis.parsing.PropertyParser`:動態屬性解析器
主要程式碼如下:
```java
public class PropertyParser {
public static String parse(String string, Properties variables) {
// <2.1> 建立 VariableTokenHandler 物件
VariableTokenHandler handler = new VariableTokenHandler(variables);
// <2.2> 建立 GenericTokenParser 物件
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// <2.3> 執行解析
return parser.parse(string);
}
}
```
`parse`方法:建立VariableTokenHandler物件和GenericTokenParser物件,然後呼叫GenericTokenParser的parse方法替換其中的動態值
#### GenericTokenParser
`org.apache.ibatis.parsing.GenericTokenParser`:通用的Token解析器
定義了是三個屬性:
```java
public class GenericTokenParser {
/**
* 開始的 Token 字串
*/
private final String openToken;
/**
* 結束的 Token 字串
*/
private final String closeToken;
/**
* Token處理器
*/
private final TokenHandler handler;
}
```
根據開始字串和結束字串解析出裡面的表示式(例如${name}->name),然後通過TokenHandler進行解析處理
#### VariableTokenHandler
`VariableTokenHandler`,是`PropertyParser`的內部靜態類,變數Token處理器,根據`Properties variables`變數物件將Token動態值解析成實際值
#### 總結
- 將XML檔案解析成`XPathParser`物件,其中會解析成對應的`Document`物件,內部的Properties物件儲存動態變數的值
- `PropertyParser`用於解析XML檔案中的動態值,根據`GenericTokenParser`獲取動態屬性的名稱(例如${name}->name),然後通過`VariableTokenHandler`根據Properties物件獲取到動態屬性(name)對應的值
### 反射模組
主要功能:對Java原生的反射進行了良好的封裝,提供更加簡單易用的API,用於解析類物件
> 反射這一模組的程式碼寫得很漂亮,值得參考!!!
主要包路徑:org.apache.ibatis.reflection
如下所示:
主要檢視以下幾個類:
- `org.apache.ibatis.reflection.Reflector`:儲存Class類中定義的屬性相關資訊並進行了簡單的對映
- `org.apache.ibatis.reflection.invoker.MethodInvoker`:Class類中屬性對應set方法或者get方法的**封裝**
- `org.apache.ibatis.reflection.DefaultReflectorFactory`:Reflector的工廠介面,用於建立和快取Reflector物件
- `org.apache.ibatis.reflection.MetaClass`:Class類的元資料,包裝Reflector,基於PropertyTokenizer(分詞器)提供對Class類的元資料一些操作,可以理解成對Reflector操作的進一步**增強**
- `org.apache.ibatis.reflection.DefaultObjectFactory`:實現了ObjectFactory工廠介面,用於建立Class類物件
- `org.apache.ibatis.reflection.wrapper.BeanWrapper`:實現了ObjectWrapper物件包裝介面,繼承BaseWrapper抽象類,根據Object物件與其MetaClass元資料物件提供對該Object物件的一些操作
- `org.apache.ibatis.reflection.MetaObject`:物件元資料,提供了操作物件的屬性等方法。 可以理解成對ObjectWrapper操作的進一步**增強**
- `org.apache.ibatis.reflection.SystemMetaObject`:用於建立MetaObject物件,提供了ObjectFactory、ObjectWrapperFactory、空MetaObject的單例
- `org.apache.ibatis.reflection.ParamNameResolver`:方法引數名稱解析器,用於解析我們定義的Mapper介面的方法
#### Reflector
`org.apache.ibatis.reflection.Reflector`:儲存Class類中定義的屬性相關資訊並進行了簡單的對映
部分程式碼如下:
```java
public class Reflector {
/**
* Class類
*/
private final Class> type;
/**
* 可讀屬性集合
*/
private final String[] readablePropertyNames;
/**
* 可寫屬性集合
*/
private final String[] writablePropertyNames;
/**
* 屬性對應的 setter 方法的對映。
*
* key 為屬性名稱
* value 為 Invoker 物件
*/
private final Map setMethods = new HashMap<>();
/**
* 屬性對應的 getter 方法的對映。
*
* key 為屬性名稱 value 為 Invoker 物件
*/
private final Map getMethods = new HashMap<>();
/**
* 屬性對應的 setter 方法的方法引數型別的對映。{@link #setMethods}
*
* key 為屬性名稱
* value 為方法引數型別
*/
private final Map> setTypes = new HashMap<>();
/**
* 屬性對應的 getter 方法的返回值型別的對映。{@link #getMethods}
*
* key 為屬性名稱
* value 為返回值的型別
*/
private final Map> getTypes = new HashMap<>();
/**
* 預設構造方法
*/
private Constructor> defaultConstructor;
/**
* 所有屬性集合
* key 為全大寫的屬性名稱
* value 為屬性名稱
*/
private Map caseInsensitivePropertyMap = new HashMap<>();
public Reflector(Class> clazz) {
// 設定對應的類
type = clazz;
// <1> 初始化 defaultConstructor 預設構造器,也就是無參構造器
addDefaultConstructor(clazz);
// <2> 初始化 getMethods 和 getTypes
addGetMethods(clazz);
// <3> 初始化 setMethods 和 setTypes
addSetMethods(clazz);
// <4> 可能有些屬性沒有get或者set方法,則直接將該Field欄位封裝成SetFieldInvoker或者GetFieldInvoker,然後分別儲存至上面4個變數中
addFields(clazz);
// <5> 初始化 readablePropertyNames、writeablePropertyNames、caseInsensitivePropertyMap 屬性
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
}
```
通過上面的程式碼可以看到`Reflector`在初始化的時候會通過反射機制進行解析該Class類,整個解析過程並不複雜,我這裡就不全部講述了,可閱讀相關程式碼,已做好註釋