mybatis解析xml配置中${xxx}占位符的代碼邏輯
涉及到的類以及關(guān)鍵邏輯
XNode
mybatis 在解析節(jié)點時使用的是 w3c 提供的 XML 解析工具,其類型為 org.w3c.dom.Node;但 mybatis 會使用 XNode 將其包裝,并且在 XNode 的構(gòu)造函數(shù)中就直接完成了占位符參數(shù)的解析:
public XNode(XPathParser xpathParser, Node node, Properties variables) {
this.xpathParser = xpathParser;
this.node = node;
this.name = node.getNodeName();
this.variables = variables;
this.attributes = parseAttributes(node); // 解析屬性
this.body = parseBody(node); // 解析 body
}PropertyParser
`XNode 在解析時調(diào)用了內(nèi)部函數(shù) parseAttributes() 和 parseBody(),這兩個方法內(nèi)部調(diào)用了靜態(tài)方法 PropertyParser#parse 來解析每一個值:
/**
* 解析標簽對象的屬性
*/
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
// 遍歷 Node 節(jié)點的屬性
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
// 使用 PropertyParser 解析占位符值
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}而 PropertyParser#parse 方法內(nèi)部的邏輯如下:
public static String parse(String string, Properties variables) {
// 內(nèi)部類實現(xiàn)的 tokenHandler,支持 ${key:default} 形式的解析
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 使用 GenericTokenParser 來解析占位符屬性
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
PropertyParser.VariableTokenHandler
靜態(tài)內(nèi)部類,實現(xiàn)了 TokenHandler 接口,該接口用來處理 token 并返回處理后的內(nèi)容。這個實現(xiàn)類支持 ${keyName:default} 這種形式的占位符參數(shù)解析,其中 default 是默認值,通過配置來決定是否啟用默認值:
private static class VariableTokenHandler implements TokenHandler {
private final Properties variables;
private final boolean enableDefaultValue;
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) {
this.variables = variables;
// 是否啟用了默認值,默認為 false,該值取自 variables['org.apache.ibatis.parsing.PropertyParser.enable-default-value']
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
// 默認值分隔符,默認為 ':',該值取自 variables['org.apache.ibatis.parsing.PropertyParser.default-value-separator']
this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
}
private String getPropertyValue(String key, String defaultValue) {
return variables == null ? defaultValue : variables.getProperty(key, defaultValue);
}
@Override
public String handleToken(String content) {
if (variables != null) {
String key = content;
if (enableDefaultValue) {
// ${key:default} 這樣的表達式可以支持 default 默認值,如果沒有指定的 key 屬性則使用默認值
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
// 分隔符之前即為 key
key = content.substring(0, separatorIndex);
// 分隔符之后即為默認值 default
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
// 若沒有找到指定的值,則最終返回配置的原本形式 ${xxx}
return "${" + content + "}";
}
}GenericTokenParser
該類實現(xiàn)了解析占位符表達式的具體邏輯。這個類被定義為可以自定義解析方式,而且支持一個字符串中多個占位符參數(shù)的解析,并且該類為 public 類。在項目中若有類似的解析場景需要,可以直接使用該類(但最好復(fù)制該類代碼進行客制化修改,防止依賴升級帶來的邏輯不一致出現(xiàn),雖然概率很?。?。
該類源碼如下:
package org.apache.ibatis.parsing;
public class GenericTokenParser {
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
// 可以自定義占位符型式以及具體的參數(shù)解析實現(xiàn)
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
// 占位符屬性一般是 <elem>${xxx}</elem> 或 <elem attr1="${xxx}"/>
// 但值實際上可以這樣:"I am MRAG from ${userCity:重慶}"
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
// 用來存每次循環(huán)處理的起點下標
int offset = 0;
// 用來存儲讀過的內(nèi)容
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
do {
if (start > 0 && src[start - 1] == '\') {
// this open token is escaped. remove the backslash and continue.
// '{openToken}' 會被認為是轉(zhuǎn)義,跳過 '' 并將 '{openToken}' 看做普通字面量
// 將 [offset, start-1) 區(qū)間的內(nèi)容添加進 builder,即剛好讀到 '' 之前;然后直接拼接 {openToken}
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length(); // offset 移到 {openToken} 之后作為下一次操作起點
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset); // 將 [offset, start) 區(qū)間的內(nèi)容添加進 builder
offset = start + openToken.length(); // 然后移動 offset
int end = text.indexOf(closeToken, offset); // 然后開始找接著 offset 之后的 closeToken
while (end > -1) {
if ((end <= offset) || (src[end - 1] != '\')) {
// closeToken 沒有被轉(zhuǎn)義的條件是前面沒有反斜杠,或者 closeToken 跟 openToken 連著一起的,即中間沒有參數(shù)
// 中間沒有參數(shù)的情況,取決于 TokenHandler 如何處理;mybatis 并沒有處理這種情況
// 理論上如果 properties 含有一個空字符串 key,只要有對應(yīng)的鍵值,就可以處理
expression.append(src, offset, end - offset); // 將中間的表達式添加進 expression
break;
}
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
}
if (end == -1) {
// close token was not found.
// 找不到配套的 closeToken 就不做處理了,直接當字面量處理
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 使用 tokenHandler 進行轉(zhuǎn)義處理,然后添加進 builder
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length(); // offset 移位
}
}
// 尋找下一個 openToken;找不到就退出循環(huán)
start = text.indexOf(openToken, offset);
} while (start > -1);
if (offset < src.length) {
// 表達式后面還有字面量則將字面量添加進 builder
builder.append(src, offset, src.length - offset);
}
// 處理結(jié)束
return builder.toString();
}
}該類同時還被多個類使用,比如 SqlSourceBuilder、ForEachSqlNode、TextSqlNode。mapper 文件中編寫的 sql 使用的參數(shù)占位符、以及直接寫在 @Select @Update 等注解中的 sql,自然也使用了該類來解析。
到此這篇關(guān)于mybatis解析xml配置中${xxx}占位符的代碼邏輯的文章就介紹到這了,更多相關(guān)mybatis ${xxx}占位符內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何使用Spring AOP預(yù)處理Controller的參數(shù)
這篇文章主要介紹了如何使用Spring AOP預(yù)處理Controller的參數(shù)操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Java根據(jù)URL下載文件到本地的2種方式(大型文件與小型文件)
這篇文章主要給大家介紹了關(guān)于Java根據(jù)URL下載文件到本地的2種方式,分別是大型文件與小型文件,避免內(nèi)存溢出OOM,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-01-01
SpringBoot使用自動配置xxxAutoConfiguration
這篇文章介紹了SpringBoot自動配置xxxAutoConfiguration的使用方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-12-12
基于紅黑樹插入操作原理及java實現(xiàn)方法(分享)
下面小編就為大家分享一篇基于紅黑樹插入操作原理及java實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12
SpringBoot?如何通過?Profile?實現(xiàn)不同環(huán)境下的配置切換
SpringBoot通過profile實現(xiàn)在不同環(huán)境下的配置切換,比如常見的開發(fā)環(huán)境、測試環(huán)境、生產(chǎn)環(huán)境,SpringBoot常用配置文件主要有?2?種:properties?文件和yml文件,本文給大家詳細介紹SpringBoot?通過?Profile?實現(xiàn)不同環(huán)境下的配置切換,感興趣的朋友一起看看吧2022-08-08
java.util.NoSuchElementException原因及兩種解決方法
本文主要介紹了java.util.NoSuchElementException原因及兩種解決方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06

