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 ...
随机推荐
- Java进阶专题(二十六) 将近2万字的Dubbo原理解析,彻底搞懂dubbo
前言 前面我们研究了RPC的原理,市面上有很多基于RPC思想实现的框架,比如有Dubbo.今天就从Dubbo的SPI机制.服务注册与发现源码及网络通信过程去深入剖析下Dubbo. Dubbo架构 ...
- 【原创】Linux虚拟化KVM-Qemu分析(十一)之virtqueue
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...
- PAT (Basic Level) Practice (中文)1070 结绳 (25 分) 凌宸1642
PAT (Basic Level) Practice (中文)1070 结绳 (25 分) 凌宸1642 题目描述 给定一段一段的绳子,你需要把它们串成一条绳.每次串连的时候,是把两段绳子对折,再如下 ...
- JAP 1.0.1 以及 《JAP产品技术白皮书》正式发布
快讯 JAP 1.0.1 正式发布 <JAP产品技术白皮书>正式发布.立即获取:白皮书 JAP 1.0.1 版本内容 新增功能/支持 添加 com.fujieid.jap.core.uti ...
- Day07_36_Iterator迭代器
Iterator Iterator Iterator iterator(); 获取集合所依赖的迭代对象 通过迭代器iterator()中的方法完成集合的迭代(遍历),这种方式是所有集合通用的遍历方法. ...
- Asp.Net Core&CAP实现分布式事务
需要注意的是标题中的CAP不是指的CAP理论,而是园区大神杨晓东实现的框架,CAP框架基于本地消息表用最终一致性实现分布式事务. 本地消息表 首先我们考虑一个场景,在将用户信息更改后,需要发送一条消息 ...
- POJ 3613 快速幂+Floyd变形(求限制k条路径的最短路)
题意: 给你一个无向图,然后给了一个起点s和终点e,然后问从s到e的最短路是多少,中途有一个限制,那就是必须走k条边,路径可以反复走. 思路: 感觉很赞的一个题目,据说证明是什 ...
- LAMP环境搭建一个Discuz论坛
LAMP是Linux+Apache+Mysql/MariaDB+Perl/PHP/Python的简称.一组常用来搭建动态网站或者服务器的开源软件,本身都是各自独立的程序,但是因为常被放在一起使用,拥有 ...
- Windows中的共享文件和文件服务器
目录 共享文件的设置 默认共享 关闭默认共享 关闭共享服务 共享文件夹权限 文件服务器资源管理器的搭建 文件共享是指主动地在网络上共享自己的计算机文件.一般文件共享使用P2P模式,文件本身存在用户本人 ...
- 16.PHP_Ajax模拟服务器登录验证
Ajax模拟登陆验证 index.php <script language="javascript"> var http_request = false; ...