Mybatis-spring-boot-starter自动配置的原理分析
相信大家在使用SpringBoot的过程中,经常会使用到mybatis,通过使用mybatis-spring-boot-starter依赖进行自动配置,省去了自己依赖配置和Bean配置的很多麻烦。
有这么方便的starter,使大家不禁好奇,它究竟是怎么让我们能够不要任何配置就可以使用mybatis的,背后的原理究竟是什么?
本文将以mybatis-spring-boot-starter作为例子,探究它背后的秘密。
首先我们建立一个SpringBoot工程,并且添加mybatis-spring-boot-starter依赖(在此我们假设读者已经熟悉SpringBoot基本知识和操作):

如果我们此时启动程序,Mybatis会随程序自动启动。
starter减少了我们依赖配置和代码中对象关系映射配置,我们分成两部分分析:
第一步我们分析starter的依赖,从pom文件检查工程的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
可以看到,starter将需要的依赖全部引入,免去了我们配置的麻烦。
综上所述,spring的starter免去了我们配置的麻烦,是个很好用的系统,全文完,谢谢观看。
哈哈哈,如果文章写到这里就结束,大家肯定要打我了(读者内心:你写的这什么东西)
光靠引入依赖包并不足以说明为什么这些组件就能自动被装载,并且已经配置好了,所以我们分析下这些被引用的依赖,找到其中起作用的包。
首先mybatis,mybatis-spring都可以被忽略掉,这些就是正常的mybatis组件。
那可疑的就是这个mybatis-spring-boot-autoconfigure了。
直接打开这个jar包看里面的内容:

可疑的就是这个MybatisAutoConfiguration和spring.factories文件,其中spring.factories的内容:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
首先百度一下spring.factories,这个是Spring的SPI机制,这个文件的内容也已经说的很明显了,自动配置,连注释都给带上了,哈哈。
代码通过SPI机制加载了两个类,其中最重要的是:
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
我们打开这个类,查看其中代码:
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean { //......
可以看到,这是一个Spring配置类(因为有Configuration注解)
这个类需要在classpath中有SqlSessionFactory和SqlSessionFactoryBean定义时才会起效,并且要有一个DataSource的candidate注册到spring容器中
当这些条件都满足时,这个Configuration会注册一个SqlSessionFactorySqlSessionTemplate :
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { ...... @Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ......
而MyBatis整个数据库访问,都是围绕这两个对象展开的。
至此已经解开Mybatis自动配置的第一个问题了,还有第二个:我们定义的那些Dao是如何被扫描的?
请继续在这个类中往下看,类中间包裹了一个static subclass:
/**
* This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
* {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,
* similar to using Spring Data JPA repositories.
*/
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //......
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
这个类实现了ImportBeanDefinitionRegistrar,也就是会向Spring容器中注入一个bean定义,而这个bean定义就是MapperScannerConfigurer。
我们继续看MapperScannerConfigurer:
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
//......
标红的部分已经很能说明问题了:这个类是BeanDefinitionRegistry的后处理器,可能会修改Spring容器中的BeanDefinition,果然,在下面我们就能找到对应的方法:
/**
* {@inheritDoc}
*
* @since 1.0.2
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
} ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
其中最重要的部分就是这个标红的scan,我们跟进去看下(其实不跟进去大家应该也看出所以然来了,这里有个basePackage,是不是很熟悉?):
/**
* Perform a scan within the specified base packages,
* returning the registered bean definitions.
* <p>This method does <i>not</i> register an annotation config processor
* but rather leaves this up to the caller.
* @param basePackages the packages to check for annotated classes
* @return set of beans registered if any for tooling registration purposes (never {@code null})
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
这里就是从basePackages中扫描到所有合适的类(准确的说应该是接口),并且将这些类(接口)生成对应的BeanDefinition,这是为了让Spring在实例化你定义的Dao时,能够成功,如果没有这一步,启动肯定会报“Cannot find candidate for autowired property xxx”这类错误,因为你在工程里面声明的本来就是个接口嘛。
并且请注意到最后面标红的applyScopedProxyMode,这会为你的Dao生成代理,这也是为何你调试代码时,调用到Dao时你把鼠标放上去,显示的Dao类型是一个MapperProxy<T>。
至此,Mybatis自动加载的原理基本上可以总结如下:
1. mybatis-spring-boot-starter将mybatis需要的依赖全部引入
2. starter同时通过SPI机制引入了一个配置Class:MybatisAutoConfiguration,它负责注册SqlSessionFactory和SqlSessionTemplate到Spring容器中,我们使用Mybatis时绝大部分功能靠这两个Bean实现
3. 引入了AutoConfiguredMapperScannerRegistrar这个bean到Spring容器,它负责将MapperScanner引入Spring容器,然后MapperScanner会将工程中所有的Dao扫描出来转换为BeanDefinition并且注册到Spring容器中
4. 当开发的工程中使用了某个Dao时,Spring能够从容器中找到这个Dao对应的BeanDefinition,将其实例化并且注入,这样开发者就可以使用了,这也是为何我们只定义了Dao的接口,但是工程运行时能够有实例Bean的原因
Mybatis-spring-boot-starter自动配置的原理分析的更多相关文章
- Spring Boot的自动配置的原理浅析
一.原理描述 Spring Boot在进行SpringApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器. 二. ...
- Spring Boot的自动配置的原理
Spring Boot在进行SpringApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器. 1.1.1. ...
- Spring boot 的自动配置
Xml 配置文件 日志 Spring Boot对各种日志框架都做了支持,我们可以通过配置来修改默认的日志的配置: #设置日志级别 logging.level.org.springframework=D ...
- Spring Boot的自动配置原理及启动流程源码分析
概述 Spring Boot 应用目前应该是 Java 中用得最多的框架了吧.其中 Spring Boot 最具特点之一就是自动配置,基于Spring Boot 的自动配置,我们可以很快集成某个模块, ...
- 了解Spring Boot的自动配置
摘自:https://www.jianshu.com/p/ddb6e32e3faf Spring Boot的自动配置给开发者带来了很大的便利,当开发人员在pom文件中添加starter依赖后,mave ...
- Spring Boot的自动配置
Spring Boot的自动配置 --摘自https://www.hollischuang.com/archives/1791 随着Ruby.Groovy等动态语言的流行,相比较之下Java的开发显得 ...
- 初识Spring Boot框架(二)之DIY一个Spring Boot的自动配置
在上篇博客初识Spring Boot框架中我们初步见识了SpringBoot的方便之处,很多小伙伴可能也会好奇这个Spring Boot是怎么实现自动配置的,那么今天我就带小伙伴我们自己来实现一个简单 ...
- spring boot 系列之六:深入理解spring boot的自动配置
我们知道,spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子: Spring的JdbcTemplate是不是在Classpath里面?如果是,并 ...
- Spring Boot 排除自动配置的 4 种方法,关键时刻很有用!
Spring Boot 提供的自动配置非常强大,某些情况下,自动配置的功能可能不符合我们的需求,需要我们自定义配置,这个时候就需要排除/禁用 Spring Boot 某些类的自动化配置了. 比如:数据 ...
- 自定义spring boot的自动配置
文章目录 添加Maven依赖 创建自定义 Auto-Configuration 添加Class Conditions 添加 bean Conditions Property Conditions Re ...
随机推荐
- redis雪崩,穿透,击穿
缓存雪崩:同一时间大量key到过期时间失效,可在设置失效时间时加随机数,如果直接修改数据库,那么一定会有不一致,通过失效时间去反复刷新缩短不一致的时间, 为了避免数据一直存在,一定要设置过期时间如果通 ...
- 从 lite-apiserver 看 SuperEdge 边缘节点自治
引言 在 SuperEdge 0.2.0版本中,lite-apiserver 进行了重大的架构升级和功能增强.本文将从 lite-apiserver 实现及其与其它 SuperEdge 组件协同的角度 ...
- 简单了解Git
目录 Git命令 如何将一个新建的文件添加到Git仓库 版本控制 本地的项目丢到Gitee上 代码修改以及推送步骤 分支管理 Git命令 1.git init创建git本地仓库 2.ls 查看 ...
- vue-cli2 生成的项目打包优化(持续学习中)
1.昨天看到自己的项目每次打包后都是30M左右,就觉得这个打包后的dist文件太大了,能不能小点呢, 然后就看网上的资料,提供了好多优化的办法,但是我只用了一个,后期再不断的优化吧. 打开工程项目文件 ...
- 高精度加法(c++)
为什么要用高精度? 有时我们要进行精度较高的运算时,就要使用高精度来进行运算: 就如例题: 大整数加法 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 21965 通过数: 634 ...
- MySQL-一条sql语句的执行顺序
手写: SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN <right_tab ...
- 使用 nodejs 中的 http 模块实现几个超实用的工具
nodejs 方便了我们前端开发者进行一些服务端上的操作,可以进行无缝地衔接.像其他一些后端语言,如 php, golang, java 等,都需要一定的学习成本,而 nodejs 则就是为前端开发者 ...
- Java(56-64)【IDEA】
1.IDEA的使用 集成开发软件 eclipse IDEA 第一步:创建新的项目 第二步:空白的project 第三步:modules->java 写在src中 src->package- ...
- Linux pgrep命令
1 pgrep pgrep是一个根据名称查找进程ID的命令,返回的是进程ID,若存在当个进程,则分为不同的行返回ID(默认实现). 2 示例 查找java进程: pgrep java 上图还显示了ps ...
- JFX11+Maven+IDEA 发布跨平台应用的完美解决方案
1 概述 前几天写了两篇关于JFX+IDEA打包跨平台应用的文章,这篇是使用IDEA自带功能打包的,这篇是使用Maven进行打包的,但是效果不太满意,因为从JDK9开始实现模块化,同时JFX部分从JD ...