1. 简介
在之前的文章中, 讲解了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
赋值给相应的成员变量。