本文,我們來分享 MyBatis 的解析器模塊,對應 parsing
包。如下圖所示:
在 《精盡 MyBatis 源碼解析 —— 項目結構一覽》 中,簡單介紹了這個模塊如下:
解析器模塊,主要提供了兩個功能:
- 一個功能,是對 XPath 進行封裝,為 MyBatis 初始化時解析
mybatis-config.xml
配置文件以及映射配置文件提供支持。- 另一個功能,是為處理動態 SQL 語句中的占位符提供支持。
下面,我們就來看看具體的源碼。因為 parsing
是基礎支持層,所以建議胖友在我們講解到的類和方法中,打折斷點一起來了解。
2. XPathParser
org.apache.ibatis.parsing.XPathParser
,基於 Java XPath 解析器,用於解析 MyBatis mybatis-config.xml
和 **Mapper.xml
等 XML 配置文件。屬性如下:
// XPathParser.java |
document
屬性,XML 被解析后,生成的org.w3c.dom.Document
對象。validation
屬性,是否校驗 XML 。一般情況下,值為true
。entityResolver
屬性,org.xml.sax.EntityResolver
對象,XML 實體解析器。默認情況下,對 XML 進行校驗時,會基於 XML 文檔開始位置指定的 DTD 文件或 XSD 文件。例如說,解析mybatis-config.xml
配置文件時,會加載http://mybatis.org/dtd/mybatis-3-config.dtd
這個 DTD 文件。但是,如果每個應用啟動都從網絡加載該 DTD 文件,勢必在弱網絡下體驗非常下,甚至說應用部署在無網絡的環境下,還會導致下載不下來,那么就會出現 XML 校驗失敗的情況。所以,在實際場景下,MyBatis 自定義了 EntityResolver 的實現,達到使用本地 DTD 文件,從而避免下載網絡 DTD 文件的效果。詳細解析,見 「3. XMLMapperEntityResolver」 。- 另外,Spring 也自定義了 EntityResolver 的實現,感興趣的胖友,可以看看 《【死磕 Spring】—— IoC 之獲取驗證模型》 。
xpath
屬性,javax.xml.xpath.XPath
對象,用於查詢 XML 中的節點和元素。如果對 XPath 的使用不了解的胖友,請先跳轉 《Java XPath 解析器 - 解析 XML 文檔》 中,進行簡單學習,灰常簡單。-
variables
屬性,變量 Properties 對象,用來替換需要動態配置的屬性值。例如:<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>-
variables
的來源,即可以在常用的 Java Properties 文件中配置,也可以使用 MyBatis<property />
標簽中配置。例如:<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>- 這里配置的
username
和password
屬性,就可以替換上面的${username}
和${password}
這兩個動態屬性。 - 具體如何實現的,我們來看下面的
PropertyParser#parse(String string, Properties variables)
方法。
- 這里配置的
-
2.1 構造方法
XPathParser 的構造方法有 16 個之多,當然基本都非常相似,我們來挑選其中一個。代碼如下:
// XPathParser.java |
-
調用
#commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver)
方法,公用的構造方法邏輯。代碼如下:// XPathParser.java
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
// 創建 XPathFactory 對象
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
} -
調用
#createDocument(InputSource inputSource)
方法,將 XML 文件解析成 Document 對象。代碼如下:// XPathParser.java
/**
* 創建 Document 對象
*
* @param inputSource XML 的 InputSource 對象
* @return Document 對象
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 1> 創建 DocumentBuilderFactory 對象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation); // 設置是否驗證 XML
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() { // 實現都空的
- 就是簡單的 Java XML API 的使用,不了解的胖友,可以 Google 學習下。
2.2 eval 方法族
XPathParser 提供了一系列的 #eval*
方法,用於獲得 Boolean、Short、Integer、Long、Float、Double、String、Node 類型的元素或節點的“值”。當然,雖然方法很多,但是都是基於 #evaluate(String expression, Object root, QName returnType)
方法,代碼如下:
// XPathParser.java |
- 調用
xpath
的evaluate(String expression, Object root, QName returnType)
方法,獲得指定元素或節點的值。
2.2.1 eval 元素
eval 元素的方法,用於獲得 Boolean、Short、Integer、Long、Float、Double、String 類型的元素的值。我們以 #evalString(Object root, String expression)
方法為例子,代碼如下:
// XPathParser.java |
<1>
處,調用#evaluate(String expression, Object root, QName returnType)
方法,獲得值。其中,returnType
方法傳入的是XPathConstants.STRING
,表示返回的值是 String 類型。<2>
處,調用PropertyParser#parse(String string, Properties variables)
方法,基於variables
替換動態值,如果result
為動態值。這就是 MyBatis 如何替換掉 XML 中的動態值實現的方式。關於 PropertyParser ,我們會在 「5. PropertyParser」 詳細解析。
2.2.2 eval 節點
eval 元素的方法,用於獲得 Node 類型的節點的值。代碼如下:
// XPathParser.java |
<1>
處,返回結果有 Node 對象和數組兩種情況,根據方法參數expression
需要獲取的節點不同。-
<2>
處, 最終結果會將 Node 封裝成org.apache.ibatis.parsing.XNode
對象,主要為了動態值的替換。例如:// XNode.java
public String evalString(String expression) {
return xpathParser.evalString(node, expression);
}- 其它方法,就不詳細解析。感興趣的胖友,可以自己翻翻。
3. XMLMapperEntityResolver
org.apache.ibatis.builder.xml.XMLMapperEntityResolver
,實現 EntityResolver 接口,MyBatis 自定義 EntityResolver 實現類,用於加載本地的 mybatis-3-config.dtd
和 mybatis-3-mapper.dtd
這兩個 DTD 文件。代碼如下:
// XMLMapperEntityResolver.java |
- 代碼比較簡單,胖友自己瞅瞅哈。
4. GenericTokenParser
org.apache.ibatis.parsing.GenericTokenParser
,通用的 Token 解析器。代碼如下:
// GenericTokenParser.java |
- 代碼看起來好冗長,但是淡定,就一個
#parse(String text)
方法,循環( 因為可能不只一個 ),解析以openToken
開始,以closeToken
結束的 Token ,並提交給handler
進行處理,即<x>
處。 - 所以所以所以,胖友可以耐心看下這段邏輯,也可以忽略,大體理解就好。
- 關於
handler
這個 TokenHandler ,詳細見 「5. TokenHandler」 。當然,這也是為什么 GenericTokenParser 叫做通用的原因,而 TokenHandler 處理特定的邏輯。
5. PropertyParser
org.apache.ibatis.parsing.PropertyParser
,動態屬性解析器。代碼如下:
// PropertyParser.java |
<1>
,構造方法,修飾符為private
,禁止構造 PropertyParser 對象,因為它是一個靜態方法的工具類。<2>
,基於variables
變量,替換string
字符串中的動態屬性,並返回結果。<2.1>
,創建 VariableTokenHandler 對象。<2.2>
,創建 GenericTokenParser 對象。- 我們可以看到,
openToken = {
,closeToken = }
,這不就是上面看到的${username}
和{password}
的么。 - 同時,我們也可以看到,
handler
類型為 VariableTokenHandler ,也就是說,通過它實現自定義的處理邏輯。關於它,在 「6.1 VariableTokenHandler」 中詳細解析。
- 我們可以看到,
<2.3>
,調用GenericTokenParser#parse(String text)
方法,執行解析。
6. TokenHandler
org.apache.ibatis.parsing.TokenHandler
,Token 處理器接口。代碼如下:
// TokenHandler.java |
#handleToken(String content)
方法,處理 Token ,在 「4. GenericTokenParser」 中,我們已經看到它的調用了。
TokenHandler 有四個子類實現,如下圖所示:
- 本文暫時只解析 VariableTokenHandler 類,因為只有它在
parsing
包中,和解析器模塊相關。
6.1 VariableTokenHandler
VariableTokenHandler ,是 PropertyParser 的內部靜態類,變量 Token 處理器。具體什么用途?上面不是已經整的明明白白啦,就不重復解釋啦。
6.1.1 構造方法
// PropertyParser.java |
- 雖然看起來有一大坨的變量,但是不要怕。
variables
屬性,變量 Properties 對象。-
enableDefaultValue
屬性,是否開啟默認值功能。默認為ENABLE_DEFAULT_VALUE
,即不開啟。想要開啟,可以配置如下:<properties resource="org/mybatis/example/config.properties">
<!-- ... -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- Enable this feature -->
</properties>
-
defaultValueSeparator
屬性,默認值的分隔符。默認為KEY_DEFAULT_VALUE_SEPARATOR
,即":"
。想要修改,可以配置如下:<properties resource="org/mybatis/example/config.properties">
<!-- ... -->
<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- Change default value of separator -->
</properties>- 分隔符被修改成了
?:
。
- 分隔符被修改成了
6.1.2 handleToken
// VariableTokenHandler 類里 |