美文网首页
MyBatis印象阅读之解析工具

MyBatis印象阅读之解析工具

作者: 向光奔跑_ | 来源:发表于2019-07-22 11:39 被阅读0次

在上一章内容中,我们总结出了:

技术债

  • XPathParser
  • ReflectorFactory
  • Configuration
  • TypeAliasRegistry
  • TypeHandlerRegistry

今天我们先来解决掉第一个XPathParser。

1.MyBatis框架的结构

打开MyBatis源码,我们可以看到包组成部分中包含一个解析包:


MyBatis解析包

今天我们就来分析下这个包下的内容。

2.MyBatis框架parsing包下的GenericTokenParser

这个类不大,我们整体来看下:

public class GenericTokenParser {

  /**
   * 起始标识符
   */
  private final String openToken;
  /**
   * 结束标识符
   */
  private final String closeToken;
  /**
   * 扫描Token后的处理器
   */
  private final TokenHandler handler;

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

  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.
        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);
            offset = end + closeToken.length();
            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();
  }
}

我们如何来看这类?或者说如何来学习这个类呢?

  • 先从属性入手,看他包含哪些,这里很简单就只有三个属性

    openToken 起始标识符
    closeToken 结束标识符
    handler 扫描Token后的处理器

  • 再从构造方法入手,只有一个有参构造器,需要把属性中的所有值都传入
  • 不看具体实现,思考这个类作用:
    推测应该是根据起始、结束标识符扫描到对应的token,然后使用处理器进行处理

那么下面那个parse方法就应该是我们想到的那个逻辑了,具体代码可以自行阅读,理解其意思,我相信你也能写出来。若感觉吃力,也可根据test包下的GenericTokenParserTest调试理解。

上述逻辑讲完后,可能对于TokenHandler这个类大家还耿耿于怀,有些强迫症更是不理解浑身觉得难受,那么我们来具体看下这个接口。

2.1 TokenHandler类解析

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

这就是一个接口,结合上述的逻辑,其实就是入参是扫描到的token,之后进行实现这个接口拿到token进行处理返回。

3.MyBatis框架parsing包下的PropertyParser

先从功能来了解一下,这个类是用来解析我们的配置文件替换的。

这个类的学习需要我们局部来,首先我们继续来看他的属性和构造方法:

  //PropertyParser

 private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
  /**
   * 是否开启默认值开关,默认不开启
   */
  public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";

  /**
   * 默认的默认值分隔符是:
   */
  public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";

  private static final String ENABLE_DEFAULT_VALUE = "false";
  private static final String DEFAULT_VALUE_SEPARATOR = ":";

  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);
  }
  • VariableTokenHandler类是PropertyParser本类的一个内部静态类,它实现了TokenHandler接口,详见<3.1>
  • GenericTokenParser之前我们刚分析过,然后看这个创造的入参,我们可以清楚这里的逻辑是查询${}包含的token,然后使用内部静态类来处理

3.1 VariableTokenHandler类解析

 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;
      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) {
      return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
    }

    @Override
    public String handleToken(String content) {
      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());
          }
          if (defaultValue != null) {
            return variables.getProperty(key, defaultValue);
          }
        }
        if (variables.containsKey(key)) {
          return variables.getProperty(key);
        }
      }
      return "${" + content + "}";
    }
  }

这个类的逻辑也比较简单,我们简单概述下:

  • 从构造方法开始,设置是否开始默认属性,设置分隔符。
  • 然后实现了TokenHandler的方法,内部如果就是根据是否开关默认属性,如果开了,然后从属性中找不到解析token对应的值,则用默认值替换。不开的话,找不到则返回${}包装的属性,意思是不变。

相同,这块逻辑也比较清晰,也不多过多说明,如有不懂,可以调试test包下的PropertyParserTest类。

4.MyBatis框架parsing包下的XPathParser

OK,终于到了我们欠下技术债的类了,但其实这个类也没啥好说的,具体就是封装了Xpath包对文档进行解析,如果Xpath不熟,可阅读(这块代码不必深究,只需了解他作用即可)XPath学习

我们也来简单看下:

//XPathParser

  private final Document document;
  private boolean validation;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;

 public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    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();
  }

在解析的过程中,我们也取一个方法进行查看:

public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }
  
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
  return null;
}
return new XNode(this, node, variables);
}

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

归根到底就是使用了XPath来进行解析。这样看可能大多数同学都还是不懂,这里我们直接看test包下的一个关于这部分的例子来加深理解(个人认为知道作用即可,关于这块不用太过刨根问底)

首先准备文件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>

然后我们在test测试中选取一个方法:

    //XPathParserTest

   private String resource = "resources/nodelet_test.xml";

  @Test
  void constructorWithReaderValidationVariablesEntityResolver() throws Exception {

    try (Reader reader = Resources.getResourceAsReader(resource)) {
      XPathParser parser = new XPathParser(reader, false, null, null);
      testEvalMethod(parser);
    }
  }
  
    private void testEvalMethod(XPathParser parser) {
    assertEquals((Long) 1970L, parser.evalLong("/employee/birth_date/year"));
    assertEquals((short) 6, (short) parser.evalShort("/employee/birth_date/month"));
    assertEquals((Integer) 15, parser.evalInteger("/employee/birth_date/day"));
    assertEquals((Float) 5.8f, parser.evalFloat("/employee/height"));
    assertEquals((Double) 5.8d, parser.evalDouble("/employee/height"));
    assertEquals("${id_var}", parser.evalString("/employee/@id"));
    assertEquals(Boolean.TRUE, parser.evalBoolean("/employee/active"));
    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关系不大

今日总结

今天我们分析了关于MyBatis框架下的parsing下的类,知道了关于参数值解析相关方式,还解决了一个关于XPathPaser的技术债。我们再来看下我们欠下的债:

技术债

  • ReflectorFactory
  • Configuration
  • TypeAliasRegistry
  • TypeHandlerRegistry

相关文章

网友评论

      本文标题:MyBatis印象阅读之解析工具

      本文链接:https://www.haomeiwen.com/subject/ouhilctx.html