MyBatis源码学习之${标记}的解析

${name}解析概述

MyBatis的配置及映射文件中均可以使用${name}作为替换符,name的值定义在属性文件中,MyBatis在使用XPathParser对象解析XML文档时,会对属性及文本中的${name}进行处理,即在Properties对象中查找name对应的值,并用这个值替换${name}。在创建XNode对象时,分别调用parseAttributes方法与parseBody方法解析节点中的属性值及文本节点,这两个方法都调用PropertyParser的静态方法parse进行解析,以实现将${name}替换为属性对象中name对应的值。

​ 以上功能主要通过三个类PropertyParserGenericTokenParserVariableTokenHandler实现,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);
    }

}
posted @   beckwu  阅读(345)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示