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
类封装了Xpath
、Document
和 Entityresolver
。
Xpathparser
中各个字段的含义和功能如下所示。
private Document document; private Entityresolver entityresolver; private Properties variables; private 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
接口的实现类。
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) { } } 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) { 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
方法用于解析 boolean
、shot
、long
、int
、String
、Node
等类型的信息,它通过调用前面介绍的 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;
GenericTokenparser.parse()
方法的逻辑并不复杂,它会顺序查找 openToken
和 closeToken
,解析得到占位符的字面值,并将其交给 Tokenhandler
处理,然后将解析结果重新拼装成字符串并返回。
参考
《MyBatis技术内幕》
部分图片来源——《MyBatis技术内幕》