基础支持层——解析器模块

2019-10-24 书籍 《MyBatis技术内幕》

MyBatis基础支持层位于 Mybatis 整体架构的最底层,支撑着 Mybatis 的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块,不仅仅为 Mybatis 提供基础支撑,也可以在合适的场景中直接复用。

整体架构

这篇文章介绍MyBatis的解析器模块

Mybatis中涉及大量的XML配置文件,常见的XML解析方式:DOM、SAX和StAX。

XPath简介

MyBatis在初始化过 程中处 理mybatis-config.xml配置文件以及映射文件时 ,使用的是DOM 解析方式,并 结 合使用XPath解析XML配置文件。

正如前文所述,DOM会将整个 XML文档 加载 到内 存中并 形成树状数据结构 ,而 XPath是一种 为 查 询 XML文档 而设 计 的语 言,它 可以 与 DOM解析方式配合使用,实 现 对 XML文档 的解析。

Xpath 之于 XML 就好比 SQL 语言之于数据库。

XPathParser

Mybatis 提供的 Xpathparser 类封装了XpathDocumentEntityresolver

7

Xpathparser 中各个字段的含义和功能如下所示。

private Document document; // Document 对象 private boolean validation; //是否开启验证

private Entityresolver entityresolver; //用于加载本地 DTD 文件

private Properties variables; // mybatis- config. Xm1 中《propteries》标签定义的键值对集合 
private Xpath xpath; // Xpath 对象

默认情况下,对 XML 文档进行验证时,会根据 XML 文档开始位置指定的网址加载对应的 DTD 文件或 XSD 文件。

如果解析 mybatis- config. Xml 配置文件,默认联网加载 http: / mybatis. Org, / dtd/mybatis-3- config. Dtd 这个 DTD 文档,当网络比较慢时会导致验证过程缓慢。

在实践中往往会提前设置 Entityresolver 接口对象加载本地的 DTD 文件,从而避免联网加载 DTD 文件。

Xmlmapperentity Resolver 是 Mybatis 提供的 Entity Resolver 接口的实现类。

8

EntityResolver 接口的核心是 resolveEntity()方法,XMLMapperEntityResolver 的实 现 如下

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

  private static final String MYBATIS_CONFIG_DTD 
    = "org/apache/ibatis/builder/xml/mybatis-3-config.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);
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) 
            || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } 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());
    }
  }

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

}

回到对 XPathParser 的 分 析 ,在 XPathParser. createDocument()方法中封装 了前面介绍 的创 建Document对 象的过 程并 触 发 了加载 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 Document createDocument(InputSource inputSource) {
  // important: this must only be called AFTER common constructor
  try {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    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 {
      }
    });
    return builder.parse(inputSource);
  } catch (Exception e) {
    throw new BuilderException("Error creating document instance.  Cause: " + e, e);
  }
}
  • Xpathparser 中提供了一系列的 eval*0 方法用于解析 booleanshotlongintStringNode 等类型的信息,它通过调用前面介绍的 Xpath. Evaluate() 方法查找指定路径的节点或属性,并进行相应的类型装换。具体代码比较简单,就不贴出来了。这里需要注意的是 Xpathparser. Evalstring() 方法,其中会调用 Propertyparser. Parse() 方法处理节点中相应的默认值。

  • Propertyparser 中指定了是否开启使用默认值的功能以及默认的分隔符

  • PropertyParser.parse() 方法中会创建 Generic Tokenparser 解析器,并将默认值的处理委托给Generic Tokenparser.parse()方法。

  • Generic Tokenparser 是一个通用的字占位符解析器,其字段的含义如下:

    Private final String opentoken; //占位符的开始标记 
    private final String closetoken; //占位符的结東标记
    
    private final Tokenhandler handler; // Tokenhandler 接口的实现会按照一定的逻辑解析占位符

    GenericTokenparser.parse() 方法的逻辑并不复杂,它会顺序查找 openTokencloseToken,解析得到占位符的字面值,并将其交给 Tokenhandler 处理,然后将解析结果重新拼装成字符串并返回。

参考

  • 《MyBatis技术内幕》

  • 部分图片来源——《MyBatis技术内幕》

MyBatis 《MyBatis技术内幕》

相关推荐



版权声明




留言区

文章目录