mybatis解析xml配置中${xxx}占位符的代碼邏輯
涉及到的類以及關(guān)鍵邏輯
XNode
mybatis 在解析節(jié)點(diǎn)時(shí)使用的是 w3c 提供的 XML 解析工具,其類型為 org.w3c.dom.Node;但 mybatis 會(huì)使用 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 在解析時(shí)調(diào)用了內(nèi)部函數(shù) parseAttributes() 和 parseBody(),這兩個(gè)方法內(nèi)部調(diào)用了靜態(tài)方法 PropertyParser#parse 來(lái)解析每一個(gè)值:
/**
* 解析標(biāo)簽對(duì)象的屬性
*/
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
// 遍歷 Node 節(jié)點(diǎn)的屬性
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)部類實(shí)現(xiàn)的 tokenHandler,支持 ${key:default} 形式的解析
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 使用 GenericTokenParser 來(lái)解析占位符屬性
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
PropertyParser.VariableTokenHandler
靜態(tài)內(nèi)部類,實(shí)現(xiàn)了 TokenHandler 接口,該接口用來(lái)處理 token 并返回處理后的內(nèi)容。這個(gè)實(shí)現(xiàn)類支持 ${keyName:default} 這種形式的占位符參數(shù)解析,其中 default 是默認(rèn)值,通過(guò)配置來(lái)決定是否啟用默認(rèn)值:
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;
// 是否啟用了默認(rèn)值,默認(rèn)為 false,該值取自 variables['org.apache.ibatis.parsing.PropertyParser.enable-default-value']
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
// 默認(rèn)值分隔符,默認(rèn)為 ':',該值取自 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} 這樣的表達(dá)式可以支持 default 默認(rèn)值,如果沒(méi)有指定的 key 屬性則使用默認(rèn)值
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
// 分隔符之前即為 key
key = content.substring(0, separatorIndex);
// 分隔符之后即為默認(rèn)值 default
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
// 若沒(méi)有找到指定的值,則最終返回配置的原本形式 ${xxx}
return "${" + content + "}";
}
}GenericTokenParser
該類實(shí)現(xiàn)了解析占位符表達(dá)式的具體邏輯。這個(gè)類被定義為可以自定義解析方式,而且支持一個(gè)字符串中多個(gè)占位符參數(shù)的解析,并且該類為 public 類。在項(xiàng)目中若有類似的解析場(chǎng)景需要,可以直接使用該類(但最好復(fù)制該類代碼進(jìn)行客制化修改,防止依賴升級(jí)帶來(lái)的邏輯不一致出現(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ù)解析實(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}"/>
// 但值實(shí)際上可以這樣:"I am MRAG from ${userCity:重慶}"
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
// 用來(lái)存每次循環(huán)處理的起點(diǎn)下標(biāo)
int offset = 0;
// 用來(lái)存儲(chǔ)讀過(guò)的內(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}' 會(huì)被認(rèn)為是轉(zhuǎn)義,跳過(guò) '' 并將 '{openToken}' 看做普通字面量
// 將 [offset, start-1) 區(qū)間的內(nèi)容添加進(jìn) builder,即剛好讀到 '' 之前;然后直接拼接 {openToken}
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length(); // offset 移到 {openToken} 之后作為下一次操作起點(diǎn)
} 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)容添加進(jìn) builder
offset = start + openToken.length(); // 然后移動(dòng) offset
int end = text.indexOf(closeToken, offset); // 然后開(kāi)始找接著 offset 之后的 closeToken
while (end > -1) {
if ((end <= offset) || (src[end - 1] != '\')) {
// closeToken 沒(méi)有被轉(zhuǎn)義的條件是前面沒(méi)有反斜杠,或者 closeToken 跟 openToken 連著一起的,即中間沒(méi)有參數(shù)
// 中間沒(méi)有參數(shù)的情況,取決于 TokenHandler 如何處理;mybatis 并沒(méi)有處理這種情況
// 理論上如果 properties 含有一個(gè)空字符串 key,只要有對(duì)應(yīng)的鍵值,就可以處理
expression.append(src, offset, end - offset); // 將中間的表達(dá)式添加進(jìn) 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 就不做處理了,直接當(dāng)字面量處理
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 使用 tokenHandler 進(jìn)行轉(zhuǎn)義處理,然后添加進(jìn) builder
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length(); // offset 移位
}
}
// 尋找下一個(gè) openToken;找不到就退出循環(huán)
start = text.indexOf(openToken, offset);
} while (start > -1);
if (offset < src.length) {
// 表達(dá)式后面還有字面量則將字面量添加進(jìn) builder
builder.append(src, offset, src.length - offset);
}
// 處理結(jié)束
return builder.toString();
}
}該類同時(shí)還被多個(gè)類使用,比如 SqlSourceBuilder、ForEachSqlNode、TextSqlNode。mapper 文件中編寫(xiě)的 sql 使用的參數(shù)占位符、以及直接寫(xiě)在 @Select @Update 等注解中的 sql,自然也使用了該類來(lái)解析。
到此這篇關(guān)于mybatis解析xml配置中${xxx}占位符的代碼邏輯的文章就介紹到這了,更多相關(guān)mybatis ${xxx}占位符內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)翻轉(zhuǎn)單詞順序列
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)翻轉(zhuǎn)單詞順序列,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
淺談Java實(shí)體對(duì)象的三種狀態(tài)以及轉(zhuǎn)換關(guān)系
這篇文章主要介紹了淺談Java實(shí)體對(duì)象的三種狀態(tài)以及轉(zhuǎn)換關(guān)系,具有一定參考價(jià)值,需要的朋友可以,看看。。2017-11-11
springboot+vue實(shí)現(xiàn)oss文件存儲(chǔ)的示例代碼
對(duì)象存儲(chǔ)服務(wù)是一種海量、安全、低成本、高可靠的云存儲(chǔ)服務(wù),本文主要介紹了springboot+vue實(shí)現(xiàn)oss文件存儲(chǔ)的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02
Java中繼承thread類與實(shí)現(xiàn)Runnable接口的比較
這篇文章主要介紹了Java中繼承thread類與實(shí)現(xiàn)Runnable接口的比較的相關(guān)資料,需要的朋友可以參考下2017-06-06
mybatis-plus批處理IService的實(shí)現(xiàn)示例
這篇文章主要介紹了mybatis-plus批處理IService的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

