1. 程式人生 > >精盡 MyBatis 原始碼分析 - 基礎支援層

精盡 MyBatis 原始碼分析 - 基礎支援層

> 該系列文件是本人在學習 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類,整個解析過程並不複雜,我這裡就不全部講述了,可閱讀相關程式碼,已做好註釋