mybatis底层组件介绍-解析器XPathParser

本文深入解析了mybatis中XPathParser类的功能与实现细节,包括如何解析XML文件,使用XPath表达式查询节点,以及如何处理变量和实体解析。通过具体代码示例展示了变量替换的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

解析器模块XPathParser

XPathParser类用来解析xml文件
我们可以看下mybatis工程中附带的XPathParserTest

@Test
void constructorWithInputStreamValidationVariablesEntityResolver() throws Exception {

  try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
    XPathParser parser = new XPathParser(inputStream, false, null, null);
    System.out.println(parser.evalLong("/employee/birth_date/year"));
  }
}

其中nodelet_test.xml如下

<employee id="${id_var}">
  <blah something="that"/>
  <first_name>Jim</first_name>
  <last_name>Smith</last_name>
  <birth_date>
    <year>1970</year>
    <month>6</month>
    <day>15</day>
  </birth_date>
  <height units="ft">5.8</height>
  <weight units="lbs">200</weight>
  <active>true</active>
</employee>

程序的结果输出是1970
下面首先来看下XPathParser中都有哪些属性

public class XPathParser {

  // 代表一个文档对象
  private final Document document;
  // 是否开启校验
  private boolean validation;
  // 用来加载本地dtd文件
  private EntityResolver entityResolver;
  // xml中<properties>对应的键值对
  private Properties variables;
  // 主要属性,用来从xml中根据表达式来获取相应的值
  // 相当于用来查询xml的sql
  private XPath xpath;
}

EntityResolver

EntityResolver是用来加载本地dtd文件的,通常情况下是联网进行加载,,看下子类XMLMapperEntityResolver的源码

/**
 * Offline entity resolver for the MyBatis DTDs.
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class XMLMapperEntityResolver implements EntityResolver {

  // config.dtd是mybatis-config.xml的dtd文件,mapper.dtd是****Mapper.xml的dtd文件
  // dtd文件规定了在xml中能够出现哪些标签,并且标签的取值情况等
  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        // 从这里可以看出当需要读取指定的dtd文件时,会读取本地的dtd文件
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch (Exception e) {
      throw new SAXException(e.toString());
    }
  }
}
private InputSource getInputSource(String path, String publicId, String systemId) {
    InputSource source = null;
    if (path != null) {
      try {
        InputStream in = Resources.getResourceAsStream(path);
        source = new InputSource(in);
        source.setPublicId(publicId);
        source.setSystemId(systemId);
      } catch (IOException e) {
        // ignore, null is ok
      }
    }
    return source;
  }

构造函数

public XPathParser(InputStream inputStream) {
   commonConstructor(false, null, null);
   // 通过传入的输入流或者文件等,创建一个Document对象
   this.document = createDocument(new InputSource(inputStream));
 }
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
   this.validation = validation;
   this.entityResolver = entityResolver;
   this.variables = variables;
   XPathFactory factory = XPathFactory.newInstance();
   this.xpath = factory.newXPath();
 }

evalString

public String evalString(Object root, String expression) {
   // 调用xpath来进行解析
   String result = (String) evaluate(expression, root, XPathConstants.STRING);
   result = PropertyParser.parse(result, variables);
   return result;
 }
```
接下来看下PropertyParser的parse方法
```java
public static String parse(String string, Properties variables) {
  VariableTokenHandler handler = new VariableTokenHandler(variables);
   // 前两个参数分别为openToken和closeToken
   GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
   return parser.parse(string);
 }
```
```java
public String parse(String 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;
    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.
        // 开始寻找}
        // 用来暂存当前需要替换的placeholder的内容
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        // 将${之前未添加到builder中的内容添加进来,并且跳过${
        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 {
            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 {
          // 找到了匹配的
          // 进行变量替换
          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
```java
@Override
public String handleToken(String content) {
  if (variables != null) {
    String key = content;
    // 判断是否需要开启默认值替换
    if (enableDefaultValue) {
      // 获得当前placeholder和其默认值之间的分隔符,一般情况下默认值和变量之间的分隔符是:
      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) {
        // 判断placeholder是否有对应的配置值,如果没有使用默认值替换
        return variables.getProperty(key, defaultValue);
      }
    }
    // 从配置的variables中获取placeholder对应的内容
    if (variables.containsKey(key)) {
      return variables.getProperty(key);
    }
  }
  // 因为variables为空,无法进行placeholder的替换
  return "${" + content + "}";
}
```
看个例子
```java
@Test
  void testWithProperties() throws Exception{
    try (InputStream inputStream = Resources.getResourceAsStream("resources/student.xml")) {
      Properties properties = new Properties();
      // 设置配置值
      properties.setProperty("name" , "lxl");
      XPathParser parser = new XPathParser(inputStream, false, properties, null);
      System.out.println(parser.evalString("/student/name/text()"));
    }
  }
```
```xml
<student>
  <name>${name}</name>
</student>
```
输出结果为lxl
看下VariableTokenHandler的构造函数,使用由XPath传入的Properties,来解析是否需要开启默认值和默认值和placeholder之间的分隔符
比如${name:lxl} lxl就是默认值
```java
private VariableTokenHandler(Properties variables) {
  this.variables = variables;
  this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
  this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
}
```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值