MyBatis源码学习之${标记}的解析
${name}解析概述
MyBatis的配置及映射文件中均可以使用${name}作为替换符,name的值定义在属性文件中,MyBatis在使用XPathParser对象解析XML文档时,会对属性及文本中的${name}进行处理,即在Properties对象中查找name对应的值,并用这个值替换${name}。在创建XNode对象时,分别调用parseAttributes方法与parseBody方法解析节点中的属性值及文本节点,这两个方法都调用PropertyParser的静态方法parse进行解析,以实现将${name}替换为属性对象中name对应的值。
以上功能主要通过三个类PropertyParser,GenericTokenParser,VariableTokenHandler实现,PropertyParser是主控类,对外提供解析方法parse,GenericTokenParser的功能是提取出${name}中的标记name,而VariableTokenHandler的主要功能是根据标记name获取到属性对象中的值。
PropertyParser解读
PropertyParser是一个实用工具类,提供了静态的parse方法,是解析${name}的入口,客户调用这个方法获得${name}的替换值,parse方法代码如下:
public class PropertyParser {
// 私有的构造方法
private PropertyParser() {
// Prevent Instantiation
}
// 对外提供的静态方法,具体处理委托VariableTokenHandler及GenericTokenParser对象处理
// string:要处理的字符串,字符串中可能含有${name}
// variables:属性对象,存有name-value对
// 功能是将字符串的${name}规换为属性对象中name对应的value
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
}
GenericTokenParser解读
GenericTokenParser的主要功能是解析字符串中的${name}中的标记name,再调用TokenHandler中的方法获得name对应的值,这是一个通用的类,不仅可以处理${name},也可以处理#{name},TokenHandler是一个接口,这里使用的实现类是VariableTokenHandler。
将${name}中的${称为开始标记(openToken),}称为结束标记(closeToken),name称为标记token,GenericTokenParser持有openToken,closeToken及一个TokenHandler对象handler,它的核心方法是parse,它的核心功能是取出字符串的标记,使用handler对象获得标记对应的值,返回处理完的字符串,如下:
public class GenericTokenParser {
// 解析含有${name}标记的字符串,所处理的字符串可能有多个${name},如username=${username},password=${pwd}
// 找到第一个${username},调用handler方法获得username对应的值,与前面的字符串拚接,再找下一个${pwd},等等
public String parse(String text) {
// 判断text是否为空
if (text == null || text.isEmpty()) {
return "";
}
// search open token,查找开始标记,如${
int start = text.indexOf(openToken);
// 没有找到直接返回
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder(); //存放处理结果,并最终返回
StringBuilder expression = null; //存放当前处理${name}中的标记name
// 查找所有的${},直到没有
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} 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 + openToken.length();
// 查找结束标记}位置
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// 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);
} else {
// 获得${name}中的标记name
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.没有找到结束标记
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 有结束标记,使用handler中的方法处理标记
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
// 有没有下一个开始标记符
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
VariableTokenHandler解读
VariableTokenHandler是在PropertyParser中定义的一个 静态内部类,存有一个属性对象(Properties对象),并实现了TokenHandler接口,它在属性对象中获得标记对应的值,并返回,标记可以定义默认值,若属性对象中没有对应的标记,则使用这个默认值。默认MyBatis不支持默认值,可通过设置org.apache.ibatis.parsing.PropertyParser.enable-default-value为true使之支持默认值。
默认值设置的格式为:标记:默认值
public class PropertyParser {
// 内部静态类
private static class VariableTokenHandler implements TokenHandler {
@Override
public String handleToken(String content) {
// 如果属性集合不空
if (variables != null) {
String key = content;
// 允许定义默认值,
// 属性文件中用key:org.apache.ibatis.parsing.PropertyParser.enable-default-value定义是否使用默认值,默认为false
// 默认值的分隔符key: org.apache.ibatis.parsing.PropertyParser.default-value-separator,默认为:(分号)
if (enableDefaultValue) {
// 查找默认值的分隔符
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
// 有分隔符,获得默认值
if (separatorIndex >= 0) {
key = content.substring(0, separatorIndex);
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
// 如果找到默认值
if (defaultValue != null) {
// 仍需从属性集合对象中查找,并返回,这个方法是若key有对应的值,则返回这个值,否则返回默认值defaultValue
return variables.getProperty(key, defaultValue);
}
}
// 如果属性集合有要查找的key,则返回这个key对应的值而不管前面的默认值
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
// 如果variables为空,加开始结束标志返回
return "${" + content + "}";
}
}
}
测试代码
public class PropertyParserDemo {
public static void main(String[] args) {
// 定义一个Properties对象,存name-value
Properties properties = new Properties();
properties.setProperty("username","tom");
properties.setProperty("pwd","123456");
// parse方法解析${username},取username对应的值为tom
// 解析${pwd},取pwd对应的值为123456
String name = PropertyParser.parse("username=${username},password=${pwd}",properties);
// 输出字符串username=tom,password=123456
System.out.println(name);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix