MyBatis 源码学习(三):XML 配置文件解析和加载流程

持续创作,加速成长!这是我参与「掘金日新计划 · 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 的另外一个重载构造函数:

扫描二维码关注公众号,回复: 14267977 查看本文章
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;
}
复制代码
  1. 其中 parsed 是一个布尔值,XMLConfigBuilder 对象初始化时设置为 false,当调用一次 parse() 方法后置为 true

  2. 之后调用 parseConfiguration() 方法,传入 parser.evalNode("/configuration") 参数

  3. 最后返回 configuration 对象

注意: 此处 parseConfiguration() 方法中又出现了一个 parser 对象,需要做好区分

  • SqlSessionFactoryBuilder.build() 中, parserXMLConfigBuilder对象,其属性中有 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 对象内容,因为在之后的代码中,还会经常遇到它、再次认识它。

猜你喜欢

转载自juejin.im/post/7108356529593516039