mybatis原始碼-解析配置檔案(二)之解析的流程
1. 簡介
在之前的文章《ofollow,noindex" target="_blank">mybatis 初步使用(IDEA的Maven專案, 超詳細)
》中, 講解了mybatis
的初步使用, 並總結了以下mybatis
的執行流程:
- 通過 Resources 工具類讀取 mybatis-config.xml, 存入 Reader;
- SqlSessionFactoryBuilder 使用上一步獲得的 reader 建立 SqlSessionFactory 物件;
- 通過 sqlSessionFactory 物件獲得 SqlSession;
- SqlSession物件通過 *Mapper 方法找到對應的 SQL 語句, 執行 SQL 查詢。
- 底層通過 JDBC 查詢後獲得 ResultSet, 對每一條記錄, 根據resultMap的對映結果對映到 Student 中, 返回 List。
- 最後記得關閉 SqlSession
本系列文章深入講解第 2 步, 解析配置檔案。
2. 配置檔案解析流程分析
2.1 呼叫
配置檔案的解析過程對應的是以下的程式碼:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
很簡單的兩句程式碼:
-
通過
mybatis
的資源類Resources
讀入“mybatis-config.xml”
檔案; -
使用
SqlSessionFactoryBuilder
類生成我們需要的SqlSessionFactory
類;(真正的解析只有這一過程)
2.2 解析的目的
要理解配置檔案的解析過程, 首先要明白解析的目的是什麼, 從最直觀的呼叫程式碼來看, 是獲得SqlSessionFactory
。
但是, 從原始碼來看, 更本質的應該這麼說:
mybatis解析配置檔案最本質的目的是為了
獲得Configuration
物件
Configuration
物件, 可以理解是mybatis
的XML
檔案在程式中的化身。
2.3 XML 解析流程
build(reader)
函式裡面包含著SqlSessionFactory
的建立邏輯。
從客戶端呼叫build(reader)
函式到返回SqlSessionFactory
, 可以用如下的時序圖表示:
下面來看看各個步驟, 請記住,
mybatis解析配置檔案的本質就是獲得Configuration
物件
。
2.3.1 build(parser)
其最終呼叫以下的方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); 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. } } }
該方法:
-
建立
XMLConfigBuilder
物件; -
使用
XMLConfigBuilder
物件的方法parse()
來獲得Confiuration
物件; -
通過
build(configuration)
, 使用Confiuration
物件建立相應的SqlSessionFactory
物件。
2.3.2 new XMLConfigBuilder(...);
new XMLConfigBuilder(reader, environment, properties)
方法, 從字面上來理解就是建立一個XMLConfigBuilder
物件。
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) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
XMLConfigBuilder
類繼承於BaseBuilder
類,super(new Configuration())
對應的方法:
public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); }
也就是給BaseBuilder
類的各個成員變數賦值而已。
裡面的XpathParser物件是通過new XPathParser(reader, true, props, new XMLMapperEntityResolver())
方法而來的。
2.3.3 new XPathParser(...)
new XPathParser(reader, true, props, new XMLMapperEntityResolver())
就是建立XpathParser
的過程。
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); this.document = createDocument(new InputSource(reader)); }
呼叫了以下兩個函式:
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) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); 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 { } }); return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance.Cause: " + e, e); } }
注意這兩個函式是有先後順序的,
createDocument
函式務必在commonConstructor
函式之後執行
。
createDocument
函式, 其實就是通過 DOM 解析 XML 檔案的過程中的幾個步驟,獲得document
, 具體可以參見 「mybatis 解析配置檔案(一)之XML的DOM解析方式
」, 裡面提到了 Java 中使用 DOM 解析 XML 的步驟, 大致如下:
-
建立
DocumentBuilderFactory
物件; -
通過
DocumentBuilderFactory
建立DocumentBuilder
物件; -
通過
DocumentBuilder
, 從檔案或流中建立通過Document
物件; -
建立
XPathFactory
物件, 並通過XPathFactory
建立XPath
物件; -
通過
XPath
解析出XPathExpression
物件; -
使用
XPathExpression
在文件中搜索出相應的節點。
剛剛提到的兩個函式, 已經完成了前4部分, 獲得了Document
物件,Xpath
物件, 並返回後將其賦值給了相應的成員變數。
也就是說, 到了這一步, 我們已經獲得了XpathParser
物件, 該物件中已經含有mybatis-config.xml
檔案對應的Document Object
, 即document
和xpath
。 通過document
和xpath
,我們可以對mybatis-config.xml
進行後兩部操作操作。
後面幾個步驟, 是在XMLConfiguration
物件的parse()
函式中使用到, 詳情見2.3.5
。
2.3.4 new Configuration()
之前提到過,
配置檔案解析的本質就是獲得Configuration
物件
。
現在,Configuration
物件在解析的過程中第一次出現了。
那我們就可以返回這個物件了?
當然不是, 這個物件現在只是建立, 後續還有很多成員變數需要根據 XML 配置檔案解析後來賦值。
2.3.5 parser.parse()
這裡的parser
是XMLConfigBuilder
物件。
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
這個函式返回的Configuration
物件就是最終寫入SqlSessionFatory
對應成員變數的物件。
由於
配置檔案解析的本質就是獲得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); } }
其對應的過程就是解析 XML 配置檔案中properties, settings, typeAliases, plugins, objectFactory, objectWrapperFactory, reflectorFactory, environments, databaseIdProvider, typeHandlers, mappers , 這些子節點。
其中的evalNode
函式, 在其函式過程中, 會呼叫XParhParser
中的函式, 對xml
節點進行解析:
private Object evaluate(String expression, Object root, QName returnType) { try { return xpath.evaluate(expression, root, returnType); } catch (Exception e) { throw new BuilderException("Error evaluating XPath.Cause: " + e, e); } }
以上過程就是我們2.3.3 中提到的第5, 6 步過程。
具體的在後續的文章中在深入瞭解。
2.3.6 build(configuration)
該函式就是建立一個具體的SqlSessionFactory
物件。
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; }
就是建立DefaultSqlSessionFactory
物件, 並將configuration
賦值給相應的成員變數。
更具體的解析配置的過程, 後續分享。