持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
1. MyBatis 初始化流程
根据对 MyBatis 的初步了解,得到 MyBatis 的初始化流程,并用简易代码表示为
@Test
void test(){
// 配置文件路径信息,默认为 classpath:
String resource = ".../mybatis-config.xml";
// 获取 mybatis-config.xml 配置文件的输入流对象
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
// 传入配置文件流对象,使用 SqlSessionFactoryBuilder().build() 创建工厂函数对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 根据工厂函数方法 sqlSessionFactory.openSession() 创建连接对象
try (SqlSession session = sqlSessionFactory.openSession()) {
// 在连接会话中获取指定的 Mapper 映射接口文件
FolderMapper postMapper = session.getMapper(FolderMapper.class);
// 调用接口中定义的 SQL 方法,并通过方法注解或 XML 映射文件解析到具体 SQL 语句执行
List<FolderFlatTree> folders = postMapper.findWithSubFolders("Root");
// 验证结果
Assertions.assertEquals(3, folders.size());
}
}
}
对应的配置文件 mybatis-config.xml 已经在之前的文章 MyBatis 源码学习(二):XML 配置文件 中介绍过其内容结构,这次就来看一下源码中是如何对该配置文件进行解析的。
2. SqlSessionFactoryBuilder
说到解析 mybatis-config.xml 配置文件,总是少不了 SqlSessionFactoryBuilder 类的身影,因为 mybatis-config.xml 文件的解析就发生在该类的 build() 方法中。
SqlSessionFactoryBuilder 类的构造比较简单,其中仅仅定义了 build() 一个方法,但是却存在着许多不同的重载方法,即针对方法传参不同来执行不同的逻辑处理,而 XML 配置文件解析时调用了 InputStream 对象参数的方法。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactoryBuilder .build() 方法允许调用时传入的参数包括 Reader 对象和 InputStream 对象,这是两种不同的方式来读取配置文件,Reader 代表字符流对象,InputStream 代表字节流对象。
但是无论是哪种方式读取配置文件,最终都会走到 SqlSessionFactoryBuilder.build() 方法中进行如下操作
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
// 只解析配置文件,environment 和 properties 默认为 null
try {
// 如果配置文件读作 Reader 类型,则使用 ①
// XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 配置文件读作 InputStream 对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
通过源码可以看到,无论是哪种方式,最终都是通过构建 XMLConfigBuilder 对象来解析 XML 文件的,通过传入的配置文件参数构建得到 XMLConfigBuilder 对象,并调用对象的 parse() 方法解析配置文件。此时可以跟随源码逻辑进入到 XMLConfigBuilder 类中。
3. XMLConfigBuilder
XMLConfigBuilder 类继承了 BaseBuilder 抽象类,主要是用来通过 XML 配置得到 Configuration 对象的,在源码中可以看到 XMLConfigBuilder 类中除了构造方法外,仅有 parse() 方法是允许外部调用的,其他方法均是私有方法。
根据 SqlSessionFactoryBuilder .build() 方法中 Reader 和 InputStream 两种不同文件参数,可以看到对应的 XMLConfigBuilder 构造方法为:
// Reader
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
// InputStream
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
根据 SqlSessionFactoryBuilder .build() 方法传递参数可知,environment 和 props 为 null,而两种构造函数最终都会调用 XMLConfigBuilder 的另外一个重载构造函数:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props)
此时,读取的 mybatis-config.xml 配置文件对象传递到了 XPathParser 中,并作为初始化对象的参数。
3.1 XPathParser
XPathParser 初始化时,会将传入的 Reader 或 InputStream 对象转换成为 InputSource 对象来作为参数,并根据 InputSource 对象创建 Document 对象来存储 XML 配置文件中的内容。
至此, Reader 和 InputStream 都变成了 InputSource,即两种类型实现了殊途同归。
再说回到 XMLConfigBuilder 类,其构造方法中传入了 XPathParser 对象,则进入到以下的构造方法中
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;
}
构造方法首先初始化一个默认的 Configuration 属性对象,并将 XPathParser 对象赋值到 XMLConfigBuilder 对象的 parser 属性中。
此时 XMLConfigBuilder 对象的两个属性, configuration(Configuration 对象)和包含 XML 文件解析成的 Document 对象的 parser(XPathParser 对象)就产生了一些联系,碰头了。
3.2 parse()
XMLConfigBuilder 对象创建完成了,代码又返回到 SqlSessionFactoryBuilder.build() 方法中,接下来执行的是
// parser 即初始化完成的 XMLConfigBuilder 对象
return build(parser.parse());
还记得刚才说到 XMLConfigBuilder 类,除了构造方法外仅剩下一个可以外部调用的方法,就是 parse() 方法,来看一下该方法的逻辑实现。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
-
其中 parsed 是一个布尔值,XMLConfigBuilder 对象初始化时设置为 false,当调用一次 parse() 方法后置为 true
-
之后调用 parseConfiguration() 方法,传入 parser.evalNode("/configuration") 参数
-
最后返回 configuration 对象
注意: 此处 parseConfiguration() 方法中又出现了一个 parser 对象,需要做好区分
-
在
SqlSessionFactoryBuilder.build()中,parser是XMLConfigBuilder对象,其属性中有XPathParser对象和Configuration对象 -
而在
XMLConfigBuilder的对象中,XPathParser对象变量名称也叫parser
3.3 parseConfiguration()
再看 XMLConfigBuilder 对象的 parseConfiguration() 方法,传入 parser.evalNode("/configuration") 作为参数。
parser(XPathParser 对象)中有一个 Documnet 对象是从 XML 配置文件解析而来,evalNode("/configuration") 方法就是对 Documnet 对象中的 configuration 标签内容进行获取。
configuration 其实就是 XML 配置文件的根标签,因此 parser.evalNode("/configuration") 会将 configuration 标签中的所有配置内容获取到,作为 XNode 对象。
然后看 parseConfiguration() 方法的逻辑处理
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(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);
}
}
方法中将 XNode 中的所有配置内容赋值到 Configuration 对象中,如
-
propertiesElement() 设置 properties 标签内容
-
settingsElement() 设置 settings 标签内容
-
databaseIdProviderElement() 设置数据库连接信息
-
environmentsElement() 设置 environments 标签内容
-
typeHandlerElement() 进行类型注册操作
-
mapperElement() 设置 mappers 标签内容,即映射文件解析
此处内容对应了 mybatis-config.xml 配置文件中的各项配置内容,可以参考文章:MyBatis 源码学习(二):XML 配置文件
最后,返回赋值完成的 Configuration 对象。
3.4 SqlSessionFactory
此时,代码逻辑再次返回到 SqlSessionFactoryBuilder .build() 方法中,不过这一次 build() 方法接收的是 Configuration 对象参数,对应了其中最后一个重载方法
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
方法中通过 DefaultSqlSessionFactory 类传入 Configuration 对象,初始化了一个默认的 SqlSessionFactory 对象,至此,千呼万唤的 qlSessionFactory 对象终于出来了!
4. Configuration
Configuration 对象是 MyBaits 中十分重要的配置对象,其中将 MyBatis 默认配置和用户自定义配置等信息写入,并在 MyBatis 整个执行过程中不断的使用,可以说是包含了 MyBatis 需要的所有信息。
今天简单的梳理一下解析、读取 XML 配置文件得到 Configuration ,并在此基础上初始化 SqlSessionFactory 对象的流程,并没有具体的介绍 Configuration 对象内容,因为在之后的代码中,还会经常遇到它、再次认识它。