MyBatis源码分析(三):解析器模块

发布时间:2024年01月04日

目录

1、前言

2、源码分析

2.1、XPathParser

2.1.1、属性讲解

2.1.2、构造方法讲解

2.2、eval 方法

2.2.1、eval 元素

2.2.2、eval 节点

2.3、XMLMapperEntityResolver

2.4、PropertyParser

2.5、GenericTokenParser

2.6、TokenHandler

2.6.1?VariableTokenHandler

2.6.2??handleToken

3、总结


1、前言

上一篇我们了解了MyBatis的项目架构

MyBatis 源码分析(二):项目结构-CSDN博客

本篇来分享Mybatis解析器模块

  • 该模块对XPATH进行分装,解析mybatis-config.xml?配置文件以及映射配置文件
  • 该模块支持处理动态 SQL 语句中的占位符

2、源码分析

下面我们就具体来看源码:

2.1、XPathParser

XPathParser?是基于 Java?XPath?解析器,用于解析??mybatisconfig.xml?和?**Mapper.xml?等 XML 配置文件,DOM会将整个XML文档加载到内存中形成数据结构

代码属性如下:

public class XPathParser {

  /**
   * XML Document 对象
   */
  private final Document document;

  /**
   * 是否校验
   */
  private boolean validation;
  /**
   * xml实体解析器
   */
  private EntityResolver entityResolver;

  /**
   * Properties对象
   */
  private Properties variables;

  /**
   * 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;
  }

  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);
    return PropertyParser.parse(result, variables);
  }

  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<XNode> evalNodes(String expression) {
    return evalNodes(document, expression);
  }

  public List<XNode> evalNodes(Object root, String expression) {
    List<XNode> 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);
  }

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

  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 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
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

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

}

从代码类中我们可以看到XPathParser 有五个属性,同时还有很多构造方法

2.1.1、属性讲解

1、docemuet:xml对象,当XML 被解析后,会生成的?org.w3c.dom.Document?对象。

2、validation:校验开关 ,是否校验XML格式,默认为true

3、ventityResolver?: XML 实体解析器,默认情况下,对 XML 进行校验时,会基于 XML 文档开始位置指定的 DTD 文件或 XSD 文件。

4、xpath:javax.xml.xpath.XPath?对象,用于查询 XML 中的节点和元素。

5、variables?:Properties 对象,解析动态sql的时候,用来替换需要动态配置的属性值。variables?的来源可以在Java的配置文件总配置,也可以在Mybatis 的<property />配置

2.1.2、构造方法讲解

由于构造方法比较多,而且基本是类似,文章里就不一一讲解,拿出一个例子讲解一下,

例如以下代码:已经在代码中写好了注释,就不单独再写了哦。

 /**
   * 构造 XPathParser 对象
   *
   * @param xml XML 文件地址
   * @param validation 是否校验 XML
   * @param variables Properties 对象
   * @param entityResolver XML 实体解析器
   */

  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 void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    // 在此处创建 XPathFactory 对象
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

 /**
   * 创建 Document 对象
   *
   * @param inputSource XML 的 InputSource 对象
   * @return Document 对象
   */
  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      //step1:创建 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);
      //step2: 创建 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
        }
      });
      //setp3解析XML
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

2.2、eval 方法

XPathParser eval方法主要是用于获得 String、Node 、Boolean、Short、Integer、Long、Float、Double类型的元素或节点的值。我们可以很多的eval方法,都是基于evaluate(String expression, Object root, QName returnType)?方法。调用该方法可以获取到元素或节点的值。

代码如下:

/**
 * 获得指定元素或节点的值
 *
 * @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);
    }
  }

2.2.1、eval 元素

eval 元素的方法,用于获得 String、Node 、Boolean、Short、Integer、Long、Float、Double类型元素的值。

代码如下:

  public String evalString(Object root, String expression) {
    // 获取值
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    // 动态替换值 这里很重要哦  MyBatis 替换掉 XML 中的动态值就是这里实现的,
    return PropertyParser.parse(result, variables);
  }

2.2.2、eval 节点

eval 元素的方法,用于获得 Node 类型的节点的值。

代码如下:

  /**
   * 获得 Node 类型的节点
   * @param expression node数组
   * @return
   */
  public List<XNode> evalNodes(String expression) {
    // 注意 根据方法参数 expression 需要获取的节点不同 ,这里可能返回Node 对象也可能是数组,
    return evalNodes(document, expression);
  }

  public List<XNode> evalNodes(Object root, String expression) {
    // 首先获得到Node数组
    // zhuyi
    List<XNode> xnodes = new ArrayList<>();
    // 封装成 XNode 数组
    // org.apache.ibatis.parsing.XNode XNode对象 ,这里主要是为了动态值得替换
    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;
  }

2.3、XMLMapperEntityResolver

XMLMapperEntityResolver主要是用于加载本地的?mybatis-3-config.dtd?和?mybatis-3-mapper.dtd文件

DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块

public class XMLMapperEntityResolver implements EntityResolver {

  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";


  // mybatis-config.dtd 文件
  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  //  mybatis-mapper.dtd 文件
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

  /**
   * Converts a public DTD into a local one.
   *
   * @param publicId The public id that is what comes after "PUBLIC"
   * @param systemId The system id that is what comes after the public id.
   * @return The InputSource for the DTD
   * @throws org.xml.sax.SAXException If anything goes wrong
   */
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        }
        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;
  }

}

2.4、PropertyParser

动态属性解析器,代码如下:

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.
   * <p>
   * 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}}).
   * </p>
   *
   * @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.
   * <p>
   * The default separator is {@code ":"}.
   * </p>
   *
   * @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
  }

  /**
   * 重点在这
   * @param string
   * @param variables
   * @return
   */
  public static String parse(String string, Properties variables) {
    // step1 创建 VariableTokenHandler 对象
    //
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    // step2 创建 GenericTokenParser 对象
    // 重点看 GenericTokenParser("${", "}"
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    // step3 执行解析
    return parser.parse(string);
  }

  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 + "}";
    }
  }

}

重点在这

  /**
   * 重点在这
   * @param string
   * @param variables
   * @return
   */
  public static String parse(String string, Properties variables) {
    // step1 创建 VariableTokenHandler 对象
    //
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    // step2 创建 GenericTokenParser 对象
    // 重点看 GenericTokenParser("${", "}"
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    // step3 执行解析
    return parser.parse(string);
  }

2.5、GenericTokenParser

?Token 解析器。代码如下:

public class GenericTokenParser {

  // 刚开始的Token
  private final String openToken;

  // 结束的Token
  private final String closeToken;
  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
    // 找到开始的 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.
        // 创建/重置 expression 对象
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        // 添加 offset 和 openToken 之间的内容,添加到 builder 中
        builder.append(src, offset, start - offset);
        // 修改 offset
        offset = start + openToken.length();
        // 寻找结束的 closeToken 的位置
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          // 转义
          if ((end <= offset) || (src[end - 1] != '\\')) {
            // 因为 endToken 前面一个位置是 \ 转义字符,所以忽略 \
            expression.append(src, offset, end - offset);
            break;
          }
          // this close token is escaped. remove the backslash and continue.
          // 添加 [offset, end - offset - 1] 和 endToken 的内容,添加到 builder 中
          expression.append(src, offset, end - offset - 1).append(closeToken);
          // 修改 offset
          offset = end + closeToken.length();
          // 寻找结束的 closeToken 的位置
          end = text.indexOf(closeToken, offset);
        }
        // 非转义
        if (end == -1) {
          // close token was not found.
          // closeToken 未找到,直接拼接
          builder.append(src, start, src.length - start);
          // 修改 offset
          offset = src.length;
        } else {
          // <x> closeToken 找到,将 expression 提交给 handler 处理 ,并将处理结果添加到 builder 中
          builder.append(handler.handleToken(expression.toString()));
          // 修改 offset
          offset = end + closeToken.length();
        }
      }
      // 继续,寻找开始的 openToken 的位置
      start = text.indexOf(openToken, offset);
    } while (start > -1);
    // 拼接剩余的部分
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }
}

代码看起来蛮长的,其实只有一个parse(String text)?方法,循环拼接。

2.6、TokenHandler

Token 处理器接口。代码如下:

暂时只解析?VariableTokenHandler 类,其他的和解析器模块无关。

public interface TokenHandler {
  /**
   * 处理 Token
   *
   * @param content Token 字符串
   * @return 处理后的结果
   */
  String handleToken(String content);
}

2.6.1?VariableTokenHandler

PropertyParser 的内部静态类,变量 Token 处理器

代码如下:

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

2.6.2??handleToken

代码如下:

@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);
        }
    }
    // 无 variables ,直接返回
    return "${" + content + "}";
}

3、总结

本文我讲解了Mybatis的解析器模块,该模块的代码逻辑还是比较清晰,后面会对其他的模块进行分析讲解,敬请期待哦!

文章来源:https://blog.csdn.net/weixin_44543482/article/details/135347174
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。