大龄菜鸟-》 mybatis 源码阅读-》 parsing 解析器模块

以前有个同事(虎哥)和我说过,每一天都要有输出,输出才能让自己得到成长。

在一点英语上面背单词时,看上面的英语视频有那么段话是这样的。如果你今天做的事和昨天的事是一样的,那么你得到的东西和昨天也是一样的。

这上面的观点我都是特别的认可。分享给大家,希望对你也是有益的

废话不多说了~ 进入主题

一: 目录总概

类不是很多。这次我可以一个一个类慢慢说,反射包中的类多,代码也多,所以只能把源码打包上传到资源中让大家下载(下载不需要积分)。

看这个图中,我说这一个模块是对xpath进行封装,是不是想问xpath是什么东西? 点击链接学习下Xpath

大龄菜鸟-》 mybatis 源码阅读-》 parsing 解析器模块_第1张图片

二: XPathParser ,PropertyParser,GenericTokenParser 这三个解析类分析。

GenericTokenParser: 看下面的注释和代码。这个还是比较简单的。

/**
 *  有的东西看起来特别多的时候,又看不懂的时候,去调试就对了。
 *  找单测。去调试
 */
package org.apache.ibatis.parsing;

/**
 * 通用的 Token 解析器
 *
 * @author Clinton Begin
 */
public class GenericTokenParser {
  /**
   * 开始的 token 字符串
   */
  private final String openToken;

  /**
   * 结束的 token 字符串
   */
  private final String closeToken;
  /**
   * token 处理器
   */
  private final TokenHandler handler;

  /**
   * 构造方法
   * @param openToken 开始的token 字符串
   * @param closeToken 结束的token字符串
   * @param handler token的处理器
   */
  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
    //  寻找开始的 openToken 的位置
    int start = text.indexOf(openToken);
    if (start == -1) {
       // 找不到,直接返回
      return text;
    }
    char[] src = text.toCharArray();
    // 起始查找位置
    int offset = 0;

    // 接收结果的字符串构造器
    final StringBuilder builder = new StringBuilder();
    // 匹配到 openToken 和 closeToken 之间的表达式
    StringBuilder expression = null;
    do {
      // 转义字符
      if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        // 因为 openToken 前面一个位置是 \ 转义字符,所以忽略 \
        // 添加 [offset, start - offset - 1] 和 openToken 的内容,添加到 builder 中
        builder.append(src, offset, start - offset - 1).append(openToken);
        // 修改 offset
        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();
            // ABCA.indexOf("A",2),从第二个下标开始,查找A第一次出现的位置
            // 返回指定字符第一次出现的字符串内的索引,以指定的索引开始搜索。
            end = text.indexOf(closeToken, offset);
          } else {
            //  append(str,offset,len)  str是一个数组。如果是 lilerong.  这个式子是append("lilerong",2,3), 那么表示从lilerong这个字符中的第2个下标开始,往后截取3个长度。-》ler
            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();
  }


  public static void main(String[] args) {
    final StringBuilder builder = new StringBuilder();
    String text="James T Kirk reporting.";
    char[] src = text.toCharArray();

    builder.append(src, 2, 3);
    System.out.println(builder.toString());
    // 输出的是:mes
  }
}

package org.apache.ibatis.parsing;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class GenericTokenParserTest {

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

    VariableTokenHandler(Map variables) {
      this.variables = variables;
    }

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

  @Test
  void shouldDemonstrateGenericTokenReplacement() {
    GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap() {
      {
        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."));
  }
  }

PropertyParser: 这个类,还是使用了通用的token处理器,代码还是比较简单,里面的单测类看下就能明白的。

/**
 *    Copyright 2009-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.parsing;

import java.util.Properties;

/**
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class PropertyParser {

  private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
  /**
   * The special property key that indicate whether enable a default value on placeholder.
   * 

* The default value is {@code false} (indicate disable a default value on placeholder) * If you specify the {@code true}, you can specify key and default value on placeholder (e.g. {@code ${db.username:postgres}}). *

* @since 3.4.2 */ public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value"; /** * The special property key that specify a separator for key and default value on placeholder. *

* The default separator is {@code ":"}. *

* @since 3.4.2 */ 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); // 调用通用的token 解析器: 开始的 token 字符串${,结束的 token 字符串:},使用的处理器为 VariableTokenHandler GenericTokenParser parser = new GenericTokenParser("${", "}", handler); return parser.parse(string); } /** * 可变 token 处理器 */ private static class VariableTokenHandler implements TokenHandler { 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) { 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 + "}"; } } } /** * Copyright 2009-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.parsing; import java.util.Properties; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; 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"); 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"); Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("c"); 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"); 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(":"); } }
/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.parsing;

import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.ibatis.builder.BuilderException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * 用来解析mybatis-config.xml 和**Mapper.xml等xml配置文件
 *
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class XPathParser {

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

  public XPathParser(String xml) {
    commonConstructor(false, null, null);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }

  public XPathParser(Reader reader) {
    commonConstructor(false, null, null);
    this.document = createDocument(new InputSource(reader));
  }

  public XPathParser(InputStream inputStream) {
    commonConstructor(false, null, null);
    this.document = createDocument(new InputSource(inputStream));
  }

  public XPathParser(Document document) {
    commonConstructor(false, null, null);
    this.document = document;
  }

  public XPathParser(String xml, boolean validation) {
    commonConstructor(validation, null, null);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }

  public XPathParser(Reader reader, boolean validation) {
    commonConstructor(validation, null, null);
    this.document = createDocument(new InputSource(reader));
  }

  public XPathParser(InputStream inputStream, boolean validation) {
    commonConstructor(validation, null, null);
    this.document = createDocument(new InputSource(inputStream));
  }

  public XPathParser(Document document, boolean validation) {
    commonConstructor(validation, null, null);
    this.document = document;
  }

  public XPathParser(String xml, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }

  public XPathParser(Reader reader, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    this.document = createDocument(new InputSource(reader));
  }

  public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    this.document = createDocument(new InputSource(inputStream));
  }

  public XPathParser(Document document, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    this.document = document;
  }

  /**
   * 构造 XPathParser 对象
   * @param xml xml 文件地址
   * @param validation 是否校验xml
   * @param variables 变量properties对象
   * @param entityResolver xml 实体解析器
   */
  public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }

  public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
  }

  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }

  public XPathParser(Document document, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = document;
  }

  public void setVariables(Properties variables) {
    this.variables = variables;
  }

  public String evalString(String expression) {
    return evalString(document, expression);
  }

  public String evalString(Object root, String expression) {
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    result = PropertyParser.parse(result, variables);
    return result;
  }

  public Boolean evalBoolean(String expression) {
    return evalBoolean(document, expression);
  }

  public Boolean evalBoolean(Object root, String expression) {
    return (Boolean) evaluate(expression, root, XPathConstants.BOOLEAN);
  }

  public Short evalShort(String expression) {
    return evalShort(document, expression);
  }

  public Short evalShort(Object root, String expression) {
    return Short.valueOf(evalString(root, expression));
  }

  public Integer evalInteger(String expression) {
    return evalInteger(document, expression);
  }

  public Integer evalInteger(Object root, String expression) {
    return Integer.valueOf(evalString(root, expression));
  }

  public Long evalLong(String expression) {
    return evalLong(document, expression);
  }

  public Long evalLong(Object root, String expression) {
    return Long.valueOf(evalString(root, expression));
  }

  public Float evalFloat(String expression) {
    return evalFloat(document, expression);
  }

  public Float evalFloat(Object root, String expression) {
    return Float.valueOf(evalString(root, expression));
  }

  public Double evalDouble(String expression) {
    return evalDouble(document, expression);
  }

  public Double evalDouble(Object root, String expression) {
    return (Double) evaluate(expression, root, XPathConstants.NUMBER);
  }

  public List evalNodes(String expression) {
    return evalNodes(document, expression);
  }

  public List evalNodes(Object root, String expression) {
    List xnodes = new ArrayList<>();
    NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
    for (int i = 0; i < nodes.getLength(); i++) {
      xnodes.add(new XNode(this, nodes.item(i), variables));
    }
    return xnodes;
  }

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

  /**
   * 获取指定元素或者节点的值
   * @param expression  表达式
   * @param root 指定节点
   * @param returnType 返回类型
   * @return 值
   */
  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 对象
   * @param inputSource inputSource xml的inputSource 对象
   * @return document 对象
   */
  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      // 1: 创建 DocumentBuilderFactory 对象
      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);
      // 2: 创建 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
        }
      });
      // 3: 解析xml文件
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

  /**
   *  公用的构造方法逻辑。代码如下
   * @param validation
   * @param variables
   * @param entityResolver
   */
  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();
  }

}

大龄菜鸟-》 mybatis 源码阅读-》 parsing 解析器模块_第2张图片

我自己看下来,这个就是可以根据地址获取和解析里面的值,相当于对xpath进行增强。其实增强体现在哪里。我还没发现。就像上篇那个发射器类。我就可能把代码读明白了几层。但是我就搞不明白为啥不停的包装又包装。

看着就觉得麻烦。也许我现在的这些观点只是我没什么远见。就像井底之蛙。自己的眼界还是小了。所以体会不了。

继续加油~

 

总结: 解析器模板的代码就在这了。我写这个只是记录自己看的一个过程。如果刚好你也在做这个事情,希望对你有帮助。如果有不对还需要改正的,希望大家都提点。谢谢

你可能感兴趣的:(mybatis源代码阅读,java)