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)文章
如何使用Spring AOP預(yù)處理Controller的參數(shù)
這篇文章主要介紹了如何使用Spring AOP預(yù)處理Controller的參數(shù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Java根據(jù)URL下載文件到本地的2種方式(大型文件與小型文件)
這篇文章主要給大家介紹了關(guān)于Java根據(jù)URL下載文件到本地的2種方式,分別是大型文件與小型文件,避免內(nèi)存溢出OOM,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01SpringBoot使用自動(dòng)配置xxxAutoConfiguration
這篇文章介紹了SpringBoot自動(dòng)配置xxxAutoConfiguration的使用方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-12-12java實(shí)現(xiàn)微信支付結(jié)果通知
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信支付結(jié)果通知,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01基于紅黑樹(shù)插入操作原理及java實(shí)現(xiàn)方法(分享)
下面小編就為大家分享一篇基于紅黑樹(shù)插入操作原理及java實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12SpringBoot?如何通過(guò)?Profile?實(shí)現(xiàn)不同環(huán)境下的配置切換
SpringBoot通過(guò)profile實(shí)現(xiàn)在不同環(huán)境下的配置切換,比如常見(jiàn)的開(kāi)發(fā)環(huán)境、測(cè)試環(huán)境、生產(chǎn)環(huán)境,SpringBoot常用配置文件主要有?2?種:properties?文件和yml文件,本文給大家詳細(xì)介紹SpringBoot?通過(guò)?Profile?實(shí)現(xiàn)不同環(huán)境下的配置切換,感興趣的朋友一起看看吧2022-08-08java.util.NoSuchElementException原因及兩種解決方法
本文主要介紹了java.util.NoSuchElementException原因及兩種解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06