Mybatis源码阅读-----解析模块

Mybatis源码 —Parsing文件解析模块

1、parseing 解析模块主要涉及的类:
在这里插入图片描述
对于xml的解析主要有dom方式解析,sax方式解析。
dom解析:基于树形方式进行xml解析,会将xml文件在加载到内存中形成一颗DOM树,然后基于这棵树的DOM节点(node)进行操作,可以方便获取各个节点的父节点,子节点以及属性值。但是,当文件特别到时候,明显会占用空间内村,导致其他程序下降。
sax解析:是基于事件模型的方式解析xml文档,向内存中读取一部分就会解析一部分,并不需要要将整个文档读入到内存中。并且当在解析过程中获取到预期结果时,将停止解析。当解析到不同类型的解节点时将会触发相应的回调函数,因此,我们可以对感兴趣的节点成注册回调函数。由于其边加载边解析,不利于维护各个节点之间的关系。
mybatis 主要通过dom和xparse的方式进行解析xml文件。XParse 更加易于查询所需的节点,可以通过路径表达式快速定位到需要解析的节点。
1、在XPathParser 类中 主要定义了如下属性

  // XML的document对象
  private final Document document;
  // 是否校验xml
  private boolean validation;
  // xml实体解析器,用于加载本地dtd文件
  private EntityResolver entityResolver;
  // 变量properties对象  如: ${username:root} 相当于username
  private Properties variables;
  // Java Xpath对象
  private XPath xpath;
EntityResolver为接口,其中的一个实现类为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-config
  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  // 本地Mybatis-mapper
  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);
        // 获取本第config-dtd, 根据getInputSource读取指定文档
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        }
        // 获取本地mapper-dtd
        else 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());
    }
  }

在XPathParser中定义了一系列构造器

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

从构造器方法中包含了加载xml文件和创建document的两个过程。

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 Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      // 创建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);
      // 创建builder并进行配置
      DocumentBuilder builder = factory.newDocumentBuilder();
      // 设置 entityResolver 接口对象
      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);
    }
  }

XPathParse 提供了一系列 eval*()方法,用于解析long , String. short , node等类型信息

/**
*  解析各种类型的节点, 最终会调用 evaluate(String expression, Object root, QName returnType)
*/ 
public Long evalLong(Object root, String expression) {
    return Long.valueOf(evalString(root, expression));
  }

public String evalString(Object root, String expression) {
   // 查找指定的类型或者属性
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    // 如果有动态值,则进行替换
    result = PropertyParser.parse(result, variables);
    return result;
  }
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、PropertyParser 动态解析器类:
该类对默认值以及分隔符进行了定义

 private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
  // 在全局配置文件中是否对<properties>的值进行默认配置
  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 = ":";

重点方法:parse()

public static String parse(String string, Properties variables) {
    // 创建变量token动态解析器
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    // 创建通用解析器
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    // 执行解析
    return parser.parse(string);
  }
   /**
   *  variables的形式如下,存储了一系列的key  value 
    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");
    String s = PropertyParser.parse("${key}", props);
   */
  private static class VariableTokenHandler implements TokenHandler {

    // variables, properties中定义定义的健值对
    private final Properties variables;
    // 默认值配置
    private final boolean enableDefaultValue;
    // 分割符
    private final String defaultValueSeparator;

    private VariableTokenHandler(Properties variables) {
      // 变量properties对象
      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);
    }

    // 获取Property中的值。如果variables为空,获取默认值,不为空,根据key获取值
    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
            key = content.substring(0, separatorIndex);
            defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
          }
          if (defaultValue != null) {
            return variables.getProperty(key, defaultValue);
          }
        }
        // 不设置默认值,在properties中进行获取
        if (variables.containsKey(key)) {
          return variables.getProperty(key);
        }
      }
      return "${" + content + "}";   // 不存在则原样返回
    }
  }

3、GenericTokenParser 通用token解析器类,该类中只有一个 parse() 方法

public class GenericTokenParser {

  private final String openToken;       // 开始的token字符转
  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;
  }

/**
*  text形式 : ${first_name} ${initial} ${last_name} reporting
*  openTokeb : ${
*  closeToken  : }
*/
  public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    // 开始寻找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; // 匹配到 openToken 和 closeToken 之间的表达式
    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) {
          // 第一个token时,创建对象
          expression = new StringBuilder();
        } else {
          // 循环解析时,相当于则将表达式进行置空,以便于接收下一个表达式
          expression.setLength(0);
        }
        // 将解析后结果进行封装
        builder.append(src, offset, start - offset);
        // 获取下一个变量的开始token位置和和结束位置
        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;
          }
        }
        // 当end=-1时,变量替换结束
        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();
        }
      }
      // 继续寻找opentoken开始的位置
      start = text.indexOf(openToken, offset);
    }

    // 拼接剩余部分
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }
}

上述代码的主要逻辑是从${username} 中取出username , 然后tokenhandler调用handtoken(content(即username)) 方法从properties中取出器真正的值。

4、XNode类,主要对Node进行进一步的封装,便于解析,获取一些属性值

public class XNode {

  private final Node node; // Node对象
  private final String name;  // Node节点的名字
  private final String body;  // 节点的内容
  private final Properties attributes;  // 节点的属性
  private final Properties variables; // 配置文件中定义的健值对
  private final XPathParser xpathParser; // 生成XNode的XPathParser对象
 }
 该类中声明了几个对于节点解析的方法,由于比较简单,就不贴出来了

由于个人水平有限,有不足或者错误之处还望指出,感激不尽!
参考资料: 《mybatis技术》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值