mybatis源码-解析配置文件(二)之解析的流程
@
1. 简介
在之前的文章《mybatis 初步使用(IDEA的Maven项目, 超详细)》中, 讲解了mybatis
的初步使用, 并总结了以下mybatis
的执行流程:
- 通过 Resources 工具类读取 mybatis-config.xml, 存入 Reader;
- SqlSessionFactoryBuilder 使用上一步获得的 reader 创建 SqlSessionFactory 对象;
- 通过 sqlSessionFactory 对象获得 SqlSession;
- SqlSession对象通过 *Mapper 方法找到对应的 SQL 语句, 执行 SQL 查询。
- 底层通过 JDBC 查询后获得 ResultSet, 对每一条记录, 根据resultMap的映射结果映射到 Student 中, 返回 List。
- 最后记得关闭 SqlSession
本系列文章深入讲解第 2 步, 解析配置文件。
2. 配置文件解析流程分析
2.1 调用
配置文件的解析过程对应的是以下的代码:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
很简单的两句代码:
- 通过
mybatis
的资源类Resources
读入“mybatis-config.xml”
文件; - 使用
SqlSessionFactoryBuilder
类生成我们需要的SqlSessionFactory
类;(真正的解析只有这一过程)
2.2 解析的目的
要理解配置文件的解析过程, 首先要明白解析的目的是什么, 从最直观的调用代码来看, 是获得SqlSessionFactory
。
但是, 从源代码来看, 更本质的应该这么说:
mybatis解析配置文件最本质的目的是为了获得
Configuration
对象
Configuration
对象, 可以理解是mybatis
的XML
文件在程序中的化身。
2.3 XML 解析流程
build(reader)
函数里面包含着SqlSessionFactory
的创建逻辑。
从客户端调用build(reader)
函数到返回SqlSessionFactory
, 可以用如下的时序图表示:
下面来看看各个步骤, 请记住,mybatis解析配置文件的本质就是获得Configuration
对象。
2.3.1 build(parser)
其最终调用以下的方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
该方法:
- 创建
XMLConfigBuilder
对象; - 使用
XMLConfigBuilder
对象的方法parse()
来获得Confiuration
对象; - 通过
build(configuration)
, 使用Confiuration
对象创建相应的SqlSessionFactory
对象。
2.3.2 new XMLConfigBuilder(...);
new XMLConfigBuilder(reader, environment, properties)
方法, 从字面上来理解就是创建一个XMLConfigBuilder
对象。
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
其最终调用的方法是这个:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
XMLConfigBuilder
类继承于BaseBuilder
类, super(new Configuration())
对应的方法:
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
也就是给BaseBuilder
类的各个成员变量赋值而已。
里面的XpathParser对象是通过new XPathParser(reader, true, props, new XMLMapperEntityResolver())
方法而来的。
2.3.3 new XPathParser(...)
new XPathParser(reader, true, props, new XMLMapperEntityResolver())
就是创建XpathParser
的过程。
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
调用了以下两个函数:
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);
}
}
注意这两个函数是有先后顺序的, createDocument
函数务必在commonConstructor
函数之后执行。
createDocument
函数, 其实就是通过 DOM 解析 XML 文件的过程中的几个步骤,获得document
, 具体可以参见 「mybatis 解析配置文件(一)之XML的DOM解析方式」, 里面提到了 Java 中使用 DOM 解析 XML 的步骤, 大致如下:
- 创建
DocumentBuilderFactory
对象;- 通过
DocumentBuilderFactory
创建DocumentBuilder
对象;- 通过
DocumentBuilder
, 从文件或流中创建通过Document
对象;- 创建
XPathFactory
对象, 并通过XPathFactory
创建XPath
对象;- 通过
XPath
解析出XPathExpression
对象;- 使用
XPathExpression
在文档中搜索出相应的节点。
刚刚提到的两个函数, 已经完成了前4部分, 获得了Document
对象, Xpath
对象, 并返回后将其赋值给了相应的成员变量。
也就是说, 到了这一步, 我们已经获得了XpathParser
对象, 该对象中已经含有 mybatis-config.xml 文件对应的 Document Object, 即document
和xpath
。 通过document
和xpath
,我们可以对 mybatis-config.xml 进行后两部操作操作。
后面几个步骤, 是在XMLConfiguration
对象的parse()
函数中使用到, 详情见 2.3.5。
2.3.4 new Configuration()
之前提到过, 配置文件解析的本质就是获得Configuration
对象。
现在, Configuration
对象在解析的过程中第一次出现了。
那我们就可以返回这个对象了?
当然不是, 这个对象现在只是创建, 后续还有很多成员变量需要根据 XML 配置文件解析后来赋值。
2.3.5 parser.parse()
这里的parser
是XMLConfigBuilder
对象。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
这个函数返回的Configuration
对象就是最终写入SqlSessionFatory
对应成员变量的对象。
由于配置文件解析的本质就是获得Configuration
对象, 因此, 这个函数就是解析的核心。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
其对应的过程就是解析 XML 配置文件中 properties, settings, typeAliases, plugins, objectFactory, objectWrapperFactory, reflectorFactory, environments, databaseIdProvider, typeHandlers, mappers, 这些子节点。
其中的evalNode
函数, 在其函数过程中, 会调用XParhParser
中的函数, 对 xml 节点进行解析:
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.3.3 中提到的第 5, 6 步过程。
具体的在后续的文章中在深入了解。
2.3.6 build(configuration)
该函数就是创建一个具体的SqlSessionFactory
对象。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
就是创建DefaultSqlSessionFactory
对象, 并将configuration
赋值给相应的成员变量。
更具体的解析配置的过程, 后续分享。
一起学 mybatis
你想不想来学习 mybatis? 学习其使用和源码呢?那么, 在博客园关注我吧!!
我自己打算把这个源码系列更新完毕, 同时会更新相应的注释。快去 star 吧!!