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 类封装了XpathDocument Entityresolver

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技术内幕》