mybatis原始碼-解析配置檔案(二)之解析的流程
1. 簡介
在之前的文章《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.
}
}
}
該方法:
1. 建立XMLConfigBuilder
物件;
2. 使用XMLConfigBuilder
物件的方法parse()
來獲得Confiuration
物件;
3. 通過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
賦值給相應的成員變數。
更具體的解析配置的過程, 後續分享。