Mybatis源码解析二——解析器模块(parsing)

解析器模块(parsing)

Mybatis源码解析一——搭建阅读环境
Mybatis源码解析二——解析器模块(parsing)
Mybatis源码解析三——反射模块(Reflector)



前言

Mybatis的parsing包主要负责解析配置文件,解析sql的工作。 建议先通篇阅读,再进入idea调试。推荐尝试先正序阅读,再逆序阅读。

推荐clone我的代码库MyBatis源码阅读,配合博文一起阅读,注释中写了很多 link,有助于源码阅读中的思路连贯。有用请star。Thanks♪(・ω・)ノ
可以选择正序阅读,是由点到面的解析(推荐)。
也可以逆序阅读,是由面到点的解析。


一、parsing包结构

在这里插入图片描述
测试类
在这里插入图片描述

二、GenericTokenParser

2.1GenericTokenParser

首先来解释GenericTokenParser类,这个类的作用是生成通用的token解析器。
先来看这个类的属性

  private final String openToken;
  private final String closeToken;
  private final TokenHandler handler;

其中 openToken 、 closeToken 是String类型, 也就是匹配字符串,以openToken开始,以colseToken结尾。
handler属性是TokenHandler类型,可以看到这是一个接口,只有一个方法。

public interface TokenHandler {
  String handleToken(String content);
}

TokenHandler接口有四个实现类,只有VariableTokenHandler这个实现类是在parsing包下,我们简单看一眼这个类。
在这里插入图片描述

全类名:org.apache.ibatis.parsing.PropertyParser.VariableTokenHandler
可以看到VariableTokenHandler是PropertyParser类的一个内部类
源码如下,我们简单看一下。

  private static class VariableTokenHandler implements TokenHandler {
    //配置项,内部继承了Hashtable,存储的是key - value键值对,使用上和HashMap一样
    private final Properties variables;
    //表示是否开启默认配置  默认false,即默认不开启
    private final boolean enableDefaultValue;
    //默认的分隔符   默认是":"
    private final String defaultValueSeparator;

    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);
    }

    private String getPropertyValue(String key, String defaultValue) {
      //主要是处理enableDefaultValue,defaultValueSeparator的配置
      //对应配置项存在就使用配置中的,否则使用默认值
      return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
    }

    @Override
    public String handleToken(String content) {
      //实现TokenHandler的接口
      //配置存在
      if (variables != null) {
        String key = content;
        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());
          }
          //若默认值不为空,尝试在配置中获取key的配置值
          //若可以获取到,则返回其配置值,否则返回默认值
          if (defaultValue != null) {
            return variables.getProperty(key, defaultValue);
          }
        }
        //判断是否存在当前key的配置,存在则返回
        if (variables.containsKey(key)) {
          return variables.getProperty(key);
        }
      }
      //配置不存在,或者没有找到对应的key的配置,返回对应的key
      return "${" + content + "}";
    }
  }

重新回到GenericTokenParser类中,看一下构造器

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

一个全参构造器,没什么可讲的。

GenericTokenParser类中,还有一个parse方法, 我们简单看一下

  public String parse(String text) {
  /**
     * 大概实现的功能是解析字符串中的特定字符,将其替换为对应的配置项中的值
     * 比如:select * from user where id = ${id}
     * 此时我们想要将${id}替换为具体的id
     * 那么openToken=“${”  closeToken="}"
     * 下面的代码就是将"${id}",依据openToken、closeToken,解析为"id"
     * 然后在调用对应的 handler.handleToken(String content) 接口从配置中获取值
     * 
     * builder.append(handler.handleToken(expression.toString()));
     * 若此处的hanlder是上面我们讲到的VariableTokenHandler
     * 说明此时可以支持配置 是否开启默认配置 和 设置默认的分隔符
   */
    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;
    do {
      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 {
            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);
    } while (start > -1);
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }

2.2GenericTokenParserTest

看完源码我们简单看下测试类,推荐看完之后先debug一下测试类中的测试方法。

简单看一下,有三个测试方法,以及一个静态内部类VariableTokenHandler,这个类还实现了TokenHandler。实现的功能非常简单,就是维护了一个HashMap。

测试方法中使用到了GenericTokenParser类,在创建时,openToken = “${” , endToken = “}” ,当然你也可以自定义为你想要的值。同时传入了VariableTokenHandler的实例。(注意这里并没有使用我们上面讲的org.apache.ibatis.parsing.PropertyParser.VariableTokenHandler类)

class GenericTokenParserTest {

  public static class VariableTokenHandler implements TokenHandler {
    private Map<String, String> variables = new HashMap<>();

    VariableTokenHandler(Map<String, String> variables) {
      this.variables = variables;
    }

    @Override
    public String handleToken(String content) {
      return variables.get(content);
    }
  }

  @Test
  void shouldDemonstrateGenericTokenReplacement() {
    /**
     * 使用自己实现的TokenHandler来构建GenericTokenParser
     * 这个实现很简单,就是根据key来获取value
     * parsing包中对TokenHandler的实现是 {@link PropertyParser.VariableTokenHandler}
     * 其核心方法{@link PropertyParser.VariableTokenHandler#handleToken(java.lang.String)}
     * 其中包含了默认值的逻辑
     */
    GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<String, String>() {
      {
        put("first_name", "James");
        put("initial", "T");
        put("last_name", "Kirk");
        put("var{with}brace", "Hiya");
        put("", "");
      }
    }));

    assertEquals("James T Kirk reporting.", parser.parse("${first_name} ${initial} ${last_name} reporting."));
    assertEquals("Hello captain James T Kirk", parser.parse("Hello captain ${first_name} ${initial} ${last_name}"));
    assertEquals("James T Kirk", parser.parse("${first_name} ${initial} ${last_name}"));
    assertEquals("JamesTKirk", parser.parse("${first_name}${initial}${last_name}"));
    assertEquals("{}JamesTKirk", parser.parse("{}${first_name}${initial}${last_name}"));
    assertEquals("}JamesTKirk", parser.parse("}${first_name}${initial}${last_name}"));

    assertEquals("}James{{T}}Kirk", parser.parse("}${first_name}{{${initial}}}${last_name}"));
    assertEquals("}James}T{Kirk", parser.parse("}${first_name}}${initial}{${last_name}"));
    assertEquals("}James}T{Kirk", parser.parse("}${first_name}}${initial}{${last_name}"));
    assertEquals("}James}T{Kirk{{}}", parser.parse("}${first_name}}${initial}{${last_name}{{}}"));
    assertEquals("}James}T{Kirk{{}}", parser.parse("}${first_name}}${initial}{${last_name}{{}}${}"));

    assertEquals("{$$something}JamesTKirk", parser.parse("{$$something}${first_name}${initial}${last_name}"));
    assertEquals("${", parser.parse("${"));
    assertEquals("${\\}", parser.parse("${\\}"));
    assertEquals("Hiya", parser.parse("${var{with\\}brace}"));
    assertEquals("", parser.parse("${}"));
    assertEquals("}", parser.parse("}"));
    assertEquals("Hello ${ this is a test.", parser.parse("Hello ${ this is a test."));
    assertEquals("Hello } this is a test.", parser.parse("Hello } this is a test."));
    assertEquals("Hello } ${ this is a test.", parser.parse("Hello } ${ this is a test."));
  }

  @Test
  void shallNotInterpolateSkippedVaiables() {
    GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<>()));

    assertEquals("${skipped} variable", parser.parse("\\${skipped} variable"));
    assertEquals("This is a ${skipped} variable", parser.parse("This is a \\${skipped} variable"));
    assertEquals("null ${skipped} variable", parser.parse("${skipped} \\${skipped} variable"));
    assertEquals("The null is ${skipped} variable", parser.parse("The ${skipped} is \\${skipped} variable"));
  }

  @Disabled("Because it randomly fails on Travis CI. It could be useful during development.")
  @Test
  void shouldParseFastOnJdk7u6() {
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> {
      // issue #760
      GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<String, String>() {
        {
          put("first_name", "James");
          put("initial", "T");
          put("last_name", "Kirk");
          put("", "");
        }
      }));

      StringBuilder input = new StringBuilder();
      for (int i = 0; i < 10000; i++) {
        input.append("${first_name} ${initial} ${last_name} reporting. ");
      }
      StringBuilder expected = new StringBuilder();
      for (int i = 0; i < 10000; i++) {
        expected.append("James T Kirk reporting. ");
      }
      assertEquals(expected.toString(), parser.parse(input.toString()));
    });
  }

三、PropertyParser

3.1PropertyParser

这个类乍一看很复杂,其实内部很简单。源码如下,这里将一些静态常亮去掉了。

public class PropertyParser {

  private PropertyParser() {
    // Prevent Instantiation
  }

  public static String parse(String string, Properties variables) {
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    return parser.parse(string);
  }

  private static class VariableTokenHandler implements TokenHandler {
    //配置项,内部继承了Hashtable,存储的是key - value键值对,使用上和HashMap一样
    private final Properties variables;
    //表示是否开启默认配置  默认false,即默认不开启
    private final boolean enableDefaultValue;
    //默认的分隔符   默认是":"
    private final String defaultValueSeparator;

    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);
    }

    private String getPropertyValue(String key, String defaultValue) {
      //主要是处理enableDefaultValue,defaultValueSeparator的配置
      //对应配置项存在就使用配置中的,否则使用默认值
      return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
    }

    @Override
    public String handleToken(String content) {
      //实现TokenHandler的接口
      //配置存在
      if (variables != null) {
        String key = content;
        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());
          }
          //若默认值不为空,尝试在配置中获取key的配置值
          //若可以获取到,则返回其配置值,否则返回默认值
          if (defaultValue != null) {
            return variables.getProperty(key, defaultValue);
          }
        }
        //判断是否存在当前key的配置,存在则返回
        if (variables.containsKey(key)) {
          return variables.getProperty(key);
        }
      }
      //配置不存在,或者没有找到对应的key的配置,返回对应的key
      return "${" + content + "}";
    }
  }

}

可以看到这个类的构造器设置为一个私有的无参构造器,说明这个类不能够在外部进行初始化。因为这个类设计的目的是作为一个工具类,可以看到其中的 org.apache.ibatis.parsing.PropertyParser#parse 方法被设计为一个static方法。

除了私有化的无参构造器和一个parse方法之外,这个类中还有一个静态内部类VariableTokenHandler,这个类就是我们在 2.1章节讲述过得。是parsing包中唯一实现了TokenHandler接口的类。

简单讲一下parse方法.

  public static String parse(String string, Properties variables) {
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    return parser.parse(string);
  }

variables参数也就是配置项,由外部传入。parse方法调用VariableTokenHandler类的构造器,得到一个解析的handler。再调用GenericTokenParser(二章节)的构造器获取到GenericTokenParser对象实例parser。然后调用parser.parse()进行解析。
具体的调用链
org.apache.ibatis.parsing.PropertyParser#parse —>
org.apache.ibatis.parsing.GenericTokenParser#parse -->
org.apache.ibatis.parsing.PropertyParser.VariableTokenHandler#handleToken

3.2PropertyParserTest

GenericTokenParserTest不同的是,GenericTokenParserTest中自己实现了TokenHandler接口,其中只支持了从配置中获取值。而PropertyParserTest中没有自定义的实现,而是使用Properties类来进行解析(Properties中有静态内部类VariableTokenHandler,实现了TokenHandler接口,并且支持了 开启默认配置 、 设置默认分隔符 的操作,这点在测试类中也有体现)

class PropertyParserTest {

  @Test
  void replaceToVariableValue() {
    Properties props = new Properties();
    //开启默认值功能
    props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
    props.setProperty("key", "value");
    props.setProperty("tableName", "members");
    props.setProperty("orderColumn", "member_id");
    props.setProperty("a:b", "c");
//    props.setProperty("a", "值存在就不会返回默认值");
    Assertions.assertThat(PropertyParser.parse("${key}", props)).isEqualTo("value");
    Assertions.assertThat(PropertyParser.parse("${key:aaaa}", props)).isEqualTo("value");
    Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${tableName:users} ORDER BY ${orderColumn:id}", props)).isEqualTo("SELECT * FROM members ORDER BY member_id");

//    props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "false");
    /**
     * 1.开启默认值转换
     *   由于没有设置分隔符,默认的分隔符是  “:”
     *   则 ${a:b} 会被解析,“:”之后的 b 会被认为是 key a 的默认值
     *      之后会使用key a查找配置,而Properties 中没有key 为 a 的配置,所以会返回默认值,也就是b
     *      尝试,在Properties增加一个key为a的配置,看看会返回什么
     *      props.setProperty("a", "值存在就不会返回默认值")
     *
     * 2.不开启默认值转换
     *   由于没有设置分隔符,默认的分隔符是  “:”
     *   不会解析 ${a:b} 中间是否存在 ":" ,认为 “a:b” 整体是一个key,此时查询就会得到预设的value,也就是c
     */
    Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("c");

    //移除 是否开启默认值的配置  此配置项默认是为false
    // 因此和   props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "false");   效果一样
    props.remove(PropertyParser.KEY_ENABLE_DEFAULT_VALUE);
    Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("c");

  }

  @Test
  void notReplace() {
    Properties props = new Properties();
    props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
    /**
     * Properties实例中没有任何的配置数据,仅设置是否开启默认配置
     * 在没有配置值的情况下
     * 实际调用的代码  {@link PropertyParser.VariableTokenHandler#handleToken(java.lang.String)}
     * 1.默认配置开启
     *    以${key}举例
     *    不包含":",所以默认值就是null.最终会返回${key}
     *    以${a:b}举例
     *    包含":",所以默认值是b,最终调用 {@link Properties#getProperty(java.lang.String, java.lang.String)}
     *    如果Properties中存在key为a的配置,则返回其配置,否则返回其默认值
     *    这里没有配置,所以返回b
     *
     * 2.默认配置关闭
     *    不会校验是否包含":",判断Properties中是否存在配置,存在返回对应配置,否则原样返回
     */
    Assertions.assertThat(PropertyParser.parse("${key}", props)).isEqualTo("${key}");
    Assertions.assertThat(PropertyParser.parse("${key}", null)).isEqualTo("${key}");

    props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "false");
    Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("${a:b}");

    props.remove(PropertyParser.KEY_ENABLE_DEFAULT_VALUE);
    Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("${a:b}");

  }

  @Test
  void applyDefaultValue() {
    Properties props = new Properties();
    props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
    Assertions.assertThat(PropertyParser.parse("${key:default}", props)).isEqualTo("default");
    Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${tableName:users} ORDER BY ${orderColumn:id}", props)).isEqualTo("SELECT * FROM users ORDER BY id");
    Assertions.assertThat(PropertyParser.parse("${key:}", props)).isEmpty();
    Assertions.assertThat(PropertyParser.parse("${key: }", props)).isEqualTo(" ");
    Assertions.assertThat(PropertyParser.parse("${key::}", props)).isEqualTo(":");
  }

  @Test
  void applyCustomSeparator() {
    /**
     * 在开启默认值的配置上增加了设置分隔符的配置
     */
    Properties props = new Properties();
    props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
    props.setProperty(PropertyParser.KEY_DEFAULT_VALUE_SEPARATOR, "?:");
    Assertions.assertThat(PropertyParser.parse("${key?:default}", props)).isEqualTo("default");
    Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${schema?:prod}.${tableName == null ? 'users' : tableName} ORDER BY ${orderColumn}", props)).isEqualTo("SELECT * FROM prod.${tableName == null ? 'users' : tableName} ORDER BY ${orderColumn}");
    Assertions.assertThat(PropertyParser.parse("${key?:}", props)).isEmpty();
    Assertions.assertThat(PropertyParser.parse("${key?: }", props)).isEqualTo(" ");
    Assertions.assertThat(PropertyParser.parse("${key?::}", props)).isEqualTo(":");
  }

}

四、XPathParser

4.1XPathParser

XPathParser在解析xml文件时,大量依赖了XPath,因此有必要先了解一下XPath解析器的相关知识。
XPath解析器
内容相对简单,只需要了解如何获得一个Document对象,以及如何获取一个Node,以及如何从一个Node获取值。

先看看XPathParser中的属性

  /**
   * XML Document对象
   */
  private final Document document;
  /**
   * 是否校验
   */
  private boolean validation;
  /**
   * XML 实体解析器
   */
  private EntityResolver entityResolver;
  /**
   * 变量 Properties 对象
   */
  private Properties variables;
  /**
   * Java XPath 对象
   */
  private XPath xpath;

XPathParser中提供了多个构造器,并且多个构造器都调用了 org.apache.ibatis.parsing.XPathParser#commonConstructor 方法

  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) 		{
    //调用通用的构造器,通过XPathFactory获取了XPath
    commonConstructor(validation, variables, entityResolver);
    //将document对象保存到XPathParser内部,此属性是final并且没有提供get方法
    //在获取文档数据时,会将document传给XPath,由XPath解析返回数据
    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();
  }

  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      //创建documentBuilder
      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
          // NOP
        }
      });
      //解析获得document
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

同时,XPathPares还提供了一系列eval方法
eval方法族

  //eval***方法中,传入符合XPath的表达式
  public Long evalLong(String expression) {
    //重载的方法,任意属性的eval方法都有一个重载方法,其中传入了document对象
    //底层调用了XPath的evaluate来获取值
    return evalLong(document, expression);
  }

  public Long evalLong(Object root, String expression) {
    return Long.valueOf(evalString(root, expression));
  }
  
  public String evalString(Object root, String expression) {
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    result = PropertyParser.parse(result, variables);
    return result;
  }
  
  private Object evaluate(String expression, Object root, QName returnType) {
    try {
      return xpath.evaluate(expression, root, returnType);
    } catch (Exception e) {
      throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
    }
  }

可以看到对于每一种类型,都有一个对应的重载方法。重载方法中,将Document对象传入,调用evaluate方法,内部调用了xpath.evaluate方法。即XPathParser在解析XML文件时,是依赖于XPath解析返回的。
我们看到,在代码

 String result = (String) evaluate(expression, root, XPathConstants.STRING);  

执行完成之后,已经通过XPath获取到了xml文档中的值,但是,我们看到之后的一行代码

result = PropertyParser.parse(result, variables);

调用了PropertyParser类(三章节)中的静态方法进行解析。考虑一种场景,我们通过XPath从xml中获取了一条sql:

select * from user where id = ${id}

而真正的id值就在我们的配置项中,则通过 PropertyParser.parse(result, variables) ,就可以获取到可执行的sql语句,比如我们的id配置为5.

select * from user where id = 5

4.2XPathParserTest

XPathParserTest中为我们展示了如何解析一个xml,其测试的xml文件路径为 resources/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 bot="YES" score="3.2">true</active>
</employee>

这里列举几个基础的测试类

@Slf4j
class XPathParserTest {
  private String resource = "resources/nodelet_test.xml";

  // InputStream Source
  @Test
  void constructorWithInputStreamValidationVariablesEntityResolver() throws Exception {
    /**
     * XPathParser:是对XPath再封装
     * {@link org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)}
     * 中调用了XPath的API获取到Document对象
     */
    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XPathParser parser = new XPathParser(inputStream, false, null, null);
      testEvalMethod(parser);
    }
  }

  private void testEvalMethod(XPathParser parser) {
    /**
     * 这里测试了一下  eval*** 方法
     * 其本质是在底层调用{@link XPathParser#evalString(java.lang.Object, java.lang.String)
     * 在方法中调用了 XPath 的 {@link XPath#evaluate(java.lang.String, java.lang.Object, javax.xml.namespace.QName)} 方法获得值
     * 然后使用 {@link PropertyParser#parse(java.lang.String, java.util.Properties)}方法来通过配置修改配置值
     */
    Long aLong = parser.evalLong("/employee/birth_date/year");
    log.info("along====>:{}", aLong);
    Long year = parser.evalNode("/employee/birth_date/year").getLongBody();
    log.info("year====>:{}", year);
    assertEquals((short) 6, (short) parser.evalShort("/employee/birth_date/month"));
    assertEquals((Integer) 15, parser.evalInteger("/employee/birth_date/day"));
    assertEquals((Integer) 15, parser.evalNode("/employee/birth_date/day").getIntBody());
    assertEquals((Float) 5.8f, parser.evalFloat("/employee/height"));
    assertEquals((Float) 5.8f, parser.evalNode("/employee/height").getFloatBody());
    assertEquals((Double) 5.8d, parser.evalDouble("/employee/height"));
    assertEquals((Double) 5.8d, parser.evalNode("/employee/height").getDoubleBody());
    assertEquals((Double) 5.8d, parser.evalNode("/employee").evalDouble("height"));
    assertEquals("${id_var}", parser.evalString("/employee/@id"));
    assertEquals("${id_var}", parser.evalNode("/employee/@id").getStringBody());
    assertEquals("${id_var}", parser.evalNode("/employee").evalString("@id"));
    assertEquals(Boolean.TRUE, parser.evalBoolean("/employee/active"));
    assertEquals(Boolean.TRUE, parser.evalNode("/employee/active").getBooleanBody());
    assertEquals(Boolean.TRUE, parser.evalNode("/employee").evalBoolean("active"));
    assertEquals(EnumTest.YES, parser.evalNode("/employee/active").getEnumAttribute(EnumTest.class, "bot"));
    assertEquals((Float) 3.2f, parser.evalNode("/employee/active").getFloatAttribute("score"));
    assertEquals((Double) 3.2d, parser.evalNode("/employee/active").getDoubleAttribute("score"));

    assertEquals("<id>${id_var}</id>", parser.evalNode("/employee/@id").toString().trim());
    assertEquals(7, parser.evalNodes("/employee/*").size());
    XNode node = parser.evalNode("/employee/height");
    assertEquals("employee/height", node.getPath());
    assertEquals("employee[${id_var}]_height", node.getValueBasedIdentifier());
  }
}

总结

至此,对于MyBatis中解析器木块的源码阅读就告一段落,各位可以多多调试测试类,也推荐clone我的代码仓库,可以对照博文一起看。注释中写了很多link,有助于源码阅读的思路连贯性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值