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

目录

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 源码分析(二):项目结构

本篇来分享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 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);
  }

  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 的配置

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 evalNodes(String expression) {
    // 注意 根据方法参数 expression 需要获取的节点不同 ,这里可能返回Node 对象也可能是数组,
    return evalNodes(document, expression);
  }

  public List evalNodes(Object root, String expression) {
    // 首先获得到Node数组
    // zhuyi
    List 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.
   * 

* 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 } /** * 重点在这 * @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 {
          //  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的解析器模块,该模块的代码逻辑还是比较清晰,大家一定要多多调试哦!


Mybatis源码解析传送门:

MyBatis源码分析(一):搭建调试环境

MyBatis源码分析(二):项目结构

你可能感兴趣的:(源码解读,mybatis,java,开发语言)