mybatis源码-解析器模块

2020-06-26  本文已影响0人  lanmi17

目录结构

myBatis.PNG

解析器主要提供的功能:

  1. 封装XPath,为mybatis初始化时解析mybatis-config.xml配置文件以及mapper映射文件提供支持
  2. 处理动态sql语句中的占位符提供支持

具体debug路径可以执行源码org.apache.ibatis.parsing 路径下的测试类


接下来,我们来看源码中的每一个类

XPathParser

先贴源码:

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

org.apache.ibatis.parsing.XPathParser 是基于 java XPath 原理来解析mybatis配置文件的

变量

变量名 释义
document xml文件最终解析成document对象
validation 是否校验
entityResolver 实体解析器
variables 解析xml为properties对象
xpath java XPath对象,用于读取xml节点和元素的值

构造方法

  // XPathParser 构造方法
  /**
   *
   * @param xml xml文件地址
   * @param validation 是否校验
   * @param variables properties 变量
   * @param entityResolver 实体解析器
   */
  public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }
 // commonConstructor 方法初始化变量
 /**
   *  构造器 初始化变量
   * @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();
  }
 // creteDocument 方法解析xml,获取document对象,具体解析逻辑可以参考java xpath解析器
 /**
   * @param inputSource
   * @return
   */
  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 得到document
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

eval 方法族

eval 方法元素

XPathParser 类里有很多eval方法,比如 evalStringealBooleanevalShortevalIntegerevalLong等方法,正如字面上的意思,是获取xml解析后Strngbooleanshortintegerlong等值
接下来以 evalString为例来看具体的转化逻辑

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

  /**
   *
   * @param expression xml里标签路径
   * @param root document对象
   * @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);
    }
  }

eval 方法节点

public List<XNode> evalNodes(String expression) { //XNode 列表
    return evalNodes(document, expression);
  }

  public List<XNode> evalNodes(Object root, String expression) { //XNode 列表
    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) { //XNode 节点
    return evalNode(document, expression);
  }

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

XNode 最终也会转成元素,查看XNode源码

  public String evalString(String expression) {
    return xpathParser.evalString(node, expression);
  }

EntityResolver

接口 EntityResolver 是具体的实体解析器,此处采用的实现是org.apache.ibatis.builder.xml.XMLMapperEntityResolver,具体作用是加载本地的 mybatis-3-config.dtd 和 mybatis-3-mapper.dtd 这两个 DTD 文件

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-3-config.dtd 文件
  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  // 本地 mybatis-3-mapper.dtd 文件
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        // 返回 mybatis-3-config.dtd
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
          //返回 mybatis-3-mapper.dtd
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          //返回 mybatis-3-mapper.dtd
          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;
  }

}

PropertyParser

XPathParser中的evalString方法中,执行evaluate方法解析root对象获取result之后,会执行 PropertyParser.parse(result, variables),这个方法的作用是进行动态属性解析,像下面这样的xml文件

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

对于 ${username}等这种动态属性就是在这里替换的

  //  XPathParser.java
  public String evalString(Object root, String expression) {
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    result = PropertyParser.parse(result, variables);
    return result;
  }
  
  // PropertyParser.java
    public static String parse(String string, Properties variables) {
    // handler 采用的是 VariableTokenHandler
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    // 创建 GenericTokenParser 对象,传入hander对象
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    //  真正去处理动态属性 
    return parser.parse(string);
  }
  

GenericTokenParser

通用的Tooken解析器,实现解析功能的方法是parse方法,根据传入的TokenHandler处理特定的逻辑,首先来看属性

 // 
 
 /**
   *      ${
   */
  private final String openToken;
  /**
   *        }
   */
  private final String closeToken;
  /**
   * VariableTokenHandler 
   */
  private final TokenHandler handler;

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

接下来看这个类里的parse方法,这块代码比较冗长,但实际上就一个作用,根据上边构造函数传进来的 openTooken、closeTooken 进行匹配,之后提供给 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);
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          // VariableTokenHandler 真正的处理
          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();
  }

VariableTokenHandler

先上源码

private static class VariableTokenHandler implements TokenHandler {
    // Properties 对象
    private final Properties variables;
    /**
     *  是否开启默认值功能,默认 ENABLE_DEFAULT_VALUE (false)
     *
     *  开启可以设置如下
     *  <properties resource="org/mybatis/example/config.properties">
     *   <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
     * </properties>
     */
    private final boolean enableDefaultValue;

    /**
     *  默认值的分隔符,默认 KEY_DEFAULT_VALUE_SEPARATOR  (:)
     *
     *  修改可以按如下配置
     *  <properties resource="org/mybatis/example/config.properties">
     *   <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>
     * </properties>
     */
    private final String defaultValueSeparator;

    private VariableTokenHandler(Properties variables) {
      this.variables = variables;
      // 是否开启默认值功能
      // org.apache.ibatis.parsing.PropertyParser.enable-default-value
      this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
      // 默认值分隔符
      // org.apache.ibatis.parsing.PropertyParser.default-value-separator
      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 + "}";
    }
  }

VariableTokenHandlerPropertyParser 的静态内部类,变量Tooken处理器,如上 variables.getProperty(key, defaultValue) 方法,有默认值时 对 ${userName} 动态属性进行替换

上一篇 下一篇

猜你喜欢

热点阅读