本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net
目的
- 复习 Spring Boot 项目中 IoC 加载的主要流程
- 了解如何配置自定义的 AutoConfiguration
- (重点) 了解 MyBatis 如何在 Spring Boot 项目中加载配置和初始化
注:本文针对的是 MyBatis 和 Spring Boot 关联内容,没有详细讲解 MyBatis 本身的配置和初始化,这部分会在专题的其他文章中再详述。
先修知识
- 需要简单了解 Spring Boot 的 IoC 加载主要流程
- 需要了解如何使用 MyBatis
- 最好了解单纯的 Java+MyBatis 项目中,MyBatis 如何加载配置和初始化
sample 项目
直接用官网提供的 Quick-Start 的 Sample 项目做本文的样例项目。别管这个项目有多简单,只有真的上手去跑,去看代码,才有感觉。
Quick Start · mybatis/spring-boot-starter Wiki · GitHub
使用下面的 curl 命令拉取 sample 项目后,按照上面官方 Wiki 文档的内容依次将所需文件内容写入项目中
curl -s https://start.spring.io/starter.tgz\
-d name=mybatis-sample\
-d artifactId=mybatis-sample\
-d dependencies=mybatis,h2\
-d baseDir=mybatis-sample\
-d type=maven-project\
| tar -xzvf -
这个项目的基本信息:
jdk17,spring-boot 3.2.4,mybatis-spring-boot-starter 3.0.3,数据库使用内存数据库 h2
mybatis-spring-boot-starter 3.0.3 这个 MAVEN 包中又包含:
- mybatis
- mybatis-spring
- mybatis-spring-boot-autoconfigure
- spring-boot-starter
- spring-boot-starter-jdbc
Sample 项目运行结果:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.4)
2024-04-01T16:47:21.323+08:00 INFO 66794 --- [mybatis-sample] [ main] c.e.m.MybatisSampleApplication : Starting MybatisSampleApplication using Java 17 with PID 66794 (/Users/xxx/Documents/study/mybatis_study/mybatis-sample/target/classes started by zhuangsuyu in /Users/xxx/Documents/study/mybatis_study/mybatis-sample)
2024-04-01T16:47:21.326+08:00 INFO 66794 --- [mybatis-sample] [ main] c.e.m.MybatisSampleApplication : No active profile set, falling back to 1 default profile: "default"
2024-04-01T16:47:22.158+08:00 INFO 66794 --- [mybatis-sample] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-04-01T16:47:22.314+08:00 INFO 66794 --- [mybatis-sample] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:a1672050-cc5a-4f4f-b0c9-b28b3b635f20 user=SA
2024-04-01T16:47:22.315+08:00 INFO 66794 --- [mybatis-sample] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2024-04-01T16:47:22.585+08:00 INFO 66794 --- [mybatis-sample] [ main] c.e.m.MybatisSampleApplication : Started MybatisSampleApplication in 1.594 seconds (process running for 1.997)
1,San Francisco,CA,US
2024-04-01T16:47:22.648+08:00 INFO 66794 --- [mybatis-sample] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2024-04-01T16:47:22.651+08:00 INFO 66794 --- [mybatis-sample] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
AutoConfiguration 的配置
我们知道 Spring Boot 的设计核心原则之一是简化配置(约定优于配置):让一些常用的配置直接默认,如果是特殊需要定制的配置,用户可以用自己的 Bean 实例去替换。
而实现 “约定优于配置” 这一原则的核心功能之一就是 AutoConfiguration
在不同的 Spring Boot 版本中,AutoConfiguration 的写法略有不同,下面展示的是不同版本中,Mybatis 官方提供的 MybatisAutoConfiguration 源码配置方式,稍作了解即可。
在 spring-boot.2.7.x 之前版本里,
在目录 META-INF/下新建文件 spring.factories
文件中内容填写:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
在 spring-boot.2.7.x 之后版本里,
在目录 META-INF/spring/下新建文件 org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中内容填写:
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
简述 SpringIoC 的加载过程
在 Spring Boot 启动过程中,我们需要知道 AbstractApplicationContext 类中有个非常关键的**refresh()**方法。
**refresh()**方法中又有两个重要的方法:
- **
invokeBeanFactoryPostProcessors(beanFactory);**调用已经注册过了的 Bean 工厂后置处理器,用来解析和加载各种配置类,注册到 beanDefinitionMap- 先分别 getBean 之前的 Bean 工厂后置处理器(如 internalConfigurationAnnotationProcessor)
- 然后利用 ConfigurationClassPostProcessor 这个 Bean 工厂后置处理器,解析各种加了
@Configuration的配置类,@ComponentScan包,@Import注解等
- **
finishBeanFactoryInitialization(beanFactory);**实例化所有剩余的单例 bean- 利用反射的方式实例化
- 根据 DI,填充属性
- 初始化
以下是一个常见的**refresh()**方法,粘贴在这里帮助大家对我们要讲的两个重点方法在哪个位置有个大致印象。
@Override
public void refresh() throws BeansException, IllegalStateException {
this.startupShutdownLock.lock();
try {
// 一些准备工作...
try {
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 【重点】调用ConfigurationClassPostProcessor解析配置类,会注册生成beanDefinitionMap
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
// 【重点】 Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch (RuntimeException | Error ex ) {
// 一些catch工作...
} finally {
contextRefresh.end();
}
} finally {
this.startupShutdownThread = null;
this.startupShutdownLock.unlock();
}
}
源码总体流程图
从 refresh() 方法中的两个重点方法出发,一步步会通过默认配置、用户自定义配置将 MyBatis 需要的信息加载到框架中。
这里我先将我整理的源码总体流程图贴在这里,后面的内容会详细、局部地去说明各个环节的执行流程。
图中左上方灰色方块底的模块是发生在 Spring Boot 包中的,绿色方块底的模块发生在 mybatis-spring 包中,图正中偏右下的红色方块底是 mybatis-spring-boot-autoconfigure 提供的能力,蓝紫色方块底是最单纯的 mybatis 包本身了。
mybatis-spring 包在各个包中起到了连接作用。

xml 配置文件的解析流程
先说 xml 配置文件。xml 配置文件一般长下面这样,文件本身放在项目的 resources 目录下。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property />
<property />
<property />
<property />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="aaaa/bbbb.xml"/>
</mappers>
</configuration>
如果配置了 mybatis-config.xml 配置文件,该文件的解析和配置是在本文所述的 refresh() 第二个核心方法(即实例化各个 bean 的阶段)发生的,如下图所示。
在调用**SqlSessionFactoryBean的factory.getObject()方法时,会在buildSqlSessionFactory()**方法中进行 parse,使用 mybatis 包中的 XMLConfigBuilder 解析 xml 配置文件。

当然,项目也可能自己注入了一个自定义的 SqlSessionFactory,仍然也是会调用到**factory.getObject()**方法的,如下所示:
package com.example.mybatissample.config;
@Configuration
@MapperScan("com.example.mybatissample")
public class MybatisConfig {
@Autowired
DataSource dataSource;
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(
@Value("classpath:/mybatis/mybatis-config.xml") Resource configLocation
) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setConfigLocation(configLocation);
return sqlSessionFactoryBean.getObject();
}
}
当然,在实际项目中,我们也可能完全不写 xml 配置文件。这样便于管理配置信息,避免配置分散在不同文件里。
Mapper 接口和 xml 映射文件的扫描加载
mapper 接口
我们知道有以下两种配置 mapper 接口类的方法,这些配置方式到底是怎么影响扫描的?
- @MapperScan,配合 @Repository(后者可以不配置,不写最多可能会导致有的版本的 idea 在注入的地方有红色波浪线)
- @Mapper,如果不使用 @MapperScan 统一扫描包,那么每个 mapper 接口上都一定要注解 @Mapper。注意,在使用了 @MapperScan 的情况下,自定义的包外面单独的 @Mapper 就没用了。
所以在大型项目中,一般都会采用 @MapperScan 的方式。
在没有 @MapperScan 的情况下,AutoConfiguredMapperScannerRegistrar 类中的 registerBeanDefinitions() 方法会在 load 阶段调用,用来生成默认的 MapperScannerConfigurer 类的 beanDefinition,该默认配置中的配置的 basePackage 是整个项目的 Application 类所在的包。
mapper 接口的扫描也发生在 SpringIoC 的生成 beanDefinition 阶段,通过 MapperScannerConfigurer

在有 @MapperScan 的情况下
MybatisAutoConfiguration 类下的 MapperScannerRegistrarNotFoundConfiguration 类的 ConditionalOnMissingBean 条件不满足,所以不会注册默认的 MapperScannerConfigurer 类的 beanDefinition,而是使用用户自定义的方式。
@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
mapper 接口的映射文件(xml)
配置 mapper 接口的映射文件
- setMapperLocations
- 在 xml 配置文件中 下的 < mapper resource=""> 标签 < mapper resdource="/mybatis/mapper/StudentMapperDAO.xml"/>
- 接口文件与映射文件在同一路径下,接口名与映射文件名相同,并且映射文件命名为接口类名时, 在 xml 配置文件中 下的 < mapper > 标签
<mapper/> - 接口文件与映射文件在同一路径下,接口名与映射文件名相同,并且映射文件命名为接口类名时, 在 xml 配置文件中 下的 < package > 标签
<mapper/>
映射文件的解析是在的 refresh() 第二个核心方法(即实例化各个 bean 的阶段)发生的解析的。
使用 mybatis 包中的 XMLConfigBuilder 解析 xml 配置文件的最后,会交由 XMLMapperBuilder 类负责映射文件的解析,生成 mappedStatements 存放在 configuration 对象中。这部分就是纯 mybatis 源码里的内容了,不在这里赘述。
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfsImpl(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginsElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlersElement(root.evalNode("typeHandlers"));
this.mappersElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}

总结
在这篇文章里,我们先梳理了 Spring Boot IoC 的基本流程,确定了 refresh() 中两个最重点的方法。通过将这两个重点方法与 MyBatis 配置加载和初始化相结合的方式,明确了 xml 配置文件、Mapper 接口以及映射文件的加载、解析分别发生在这两个方法的什么环节。
换句话说,本质上我们是先在脑海中通过 Spring Boot IoC 的基本流程绘制了一张简要地图,然后在这张地图中圈出了哪些地点是 MyBatis 加载、解析、初始化的重要地方。
核心知识点简要总结,Spring Boot IoC 的基本流程里 refresh() 中有两个重点方法,分别是为了形成 beanDefinitionMap 的**invokeBeanFactoryPostProcessors(beanFactory);和为了实例化各种剩余bean的finishBeanFactoryInitialization(beanFactory)**;mapper 接口的扫描发生在第 1 个重点方法;xml 配置文件的解析和 xml 映射文件的解析,都发生在第 2 个重点方法中。