MyBatis 原理淺析 2 ——配置解析
前言
在前文《MyBatis 原理淺析——基本原理》一文中,簡要分析了 MyBatis 的技術原理,主要是 SqlSession 和 Mapper 的相關實現原理。本文重點分析 MyBatis 的配置解析過程,從 XML 文件提取配置到 Configuration 類。
XML解析涉及到的類
XML 解析主要涉及以下幾個類:XMLConfigBuilder、XMLMapperBuilder、BaseBuilder、XNode、Configuration、XPathParser 和 Configuration 中的配置類。各個類型的關係可以簡單用下圖描述。在 SqlSessionFactoryBuilder 類中調用方法解析 XML 時使用的是 XMLConfigBuilder 類,XMLConfigBuilder 類的 parse 方法是 XML 解析的入口,解析完成後返回配置類型 Configuration。BaseBuilder 類是抽象類,提供了 typeAlias、typeHandler 配置的處理方法,也是 XMLConfigBuilder 類和 XMLMapperBuilder 類的父類。XNode 類用於存儲解析過程中遇到的節點,XPathParser 類封裝了 XPath 相關的操作,如 XML文件的載入、節點的解析等。Configuration 類存儲了解析出來的所有屬性。
XML 解析準備階段
XML 文件的解析通過在 SqlSessionFactoryBuilder 類中創建 XMLConfigBuilder 類並調用其 parse 方法開始。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } }}
在 XMLConfigBuilder 初始化時,會創建 XPathParser 類的實例,然後創建 Configuration 類的實例,保存各個參數以供後續使用。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser;}
在 XPathParser 類的初始化過程中,會創建 XPath 實例,然後讀入 XML 文件創建 Document 對象,完成 XML 文件解析的準備工作。
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); this.document = createDocument(new InputSource(inputStream));}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();}private Document createDocument(InputSource inputSource) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); ...... //初始化 factory DocumentBuilder builder = factory.newDocumentBuilder(); ...... //初始化 builder return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); }}
XML 配置解析過程
XML 配置解析是從 XMLConfigBuilder 的 parse() 方法開始的。在 parse() 方法中,首先判斷是否已經解析過,如果重複解析則拋出異常,然後解析出 XML 文件的根節點 configuration,再從根節點中依次解析出 properties、settings、typeAliases、plugins、objectFactory、objectWrapperFactory、reflectorFactory、environments、databaseIdProvider、typeHandlers、mappers節點的內容,解析完成後返回 configuration 對象。
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration;}private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}
解析過程可以分為以下幾步:
1、解析 properties :讀取 XML 中的名稱鍵值對並保存,如果引用了 properties 文件或傳入了 properties 配置,則會用新的 properties 值替換 XML 中的配置。
2、解析 settings:按照 Properties 類型存儲。
3、解析 typeAliases:typeAliases 定義了類的別名,解析後需要根據 type 的值載入相應的 class,並註冊到 typeAliasRegistry 中。
4、解析 plugins:載入 Interceptor 實現類,創建對象,並存入 configuration 中。
5、解析 objectFactory:載入 ObjectFactory 實現類,創建對象,並存入 configuration 中。
6、解析 objectWrapperFactory:載入 ObjectWrapperFactory 實現類,創建對象,並存入 configuration 中。
7、解析 reflectorFactory:載入 ReflectorFactory 實現類,創建對象,並存入 configuration 中。
8、解析 environments:根據配置的默認環境 ID,載入環境配置,創建 Environment 對象,並存入 configuration 中。Environment 對象包含了數據源和事務管理器對象。
9、解析 databaseIdProvider:載入 DatabaseIdProvider 實現類,讀取屬性配置,創建對象,獲取數據源的 databaseId 並存入 configuration。
10、解析 typeHandlers:解析屬性配置,載入實現類,創建對象,並註冊到 typeHandlerRegistry 中。
11、解析 mappers:解析 mapper 節點,如果引用了 XML 文件則載入文件並創建 XMLMapperBuilder 對象進行解析,如果引用了包或類則添加到 configuration 中。註冊 Mapper 到 MapperRegistry 時,會創建 Mapper 介面的代理實現工廠 MapperProxyFactory,掃描 Mapper 介面的註解並存入 configuration 。
Mapper 文件的解析
mapper XML 文件的解析在 XMLMapperBuilder 中實現。與 XMLConfigBuilder 一樣,XMLMapperBuilder 初始化時也會創建 XPathParser 類的實例,但會使用參數傳入的 configuration。
XML 文件的解析是在 parse 方法中完成的。首先判斷是否已經載入過,如果沒有載入過則獲取 XML 的根節點 mapper 並開始解析,解析完成後根據命名空間的配置載入相應的 Mapper 介面並添加到 configuration 中,最後載入未完成載入的 ResultMap、CacheRef 和 Statement。
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();}
configurationElement 方法實現了對 mapper 子節點的解析,如下所示,讀取了 namespace、cache-ref、cache、parameterMap、resultMap、sql、select、insert、update、delete 等節點和屬性。如果引用的類型不在此 XML 中,並且配置中也獲取不到,則會加入未完成列表,在解析下個 XML 文件時再次嘗試載入。
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mappers namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); }}
mapper 文件的解析細節比較複雜,後文再深入分析。
每周 3 篇學習筆記或技術總結,面向有一定基礎的 Java 程序員,內容涉及 Java 進階、虛擬機、MySQL、NoSQL、分散式計算、開源框架等多個領域。關注作者或微信公眾號 backend-develop 第一時間獲取最新內容。
MyBatis 原理淺析 2 --配置解析推薦閱讀:
※Mybatis7:查詢緩存
※Mybatis1:基礎
※如何在mybatis中調試查看生成的sql語句?
※Mybatis4:訂單商品模型分析+高級映射
TAG:MyBatis |