相信大家在使用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自动配置的原理分析的更多相关文章

  1. Spring Boot的自动配置的原理浅析

    一.原理描述 Spring Boot在进行SpringApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器. 二. ...

  2. Spring Boot的自动配置的原理

    Spring Boot在进行SpringApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器. 1.1.1.   ...

  3. Spring boot 的自动配置

    Xml 配置文件 日志 Spring Boot对各种日志框架都做了支持,我们可以通过配置来修改默认的日志的配置: #设置日志级别 logging.level.org.springframework=D ...

  4. Spring Boot的自动配置原理及启动流程源码分析

    概述 Spring Boot 应用目前应该是 Java 中用得最多的框架了吧.其中 Spring Boot 最具特点之一就是自动配置,基于Spring Boot 的自动配置,我们可以很快集成某个模块, ...

  5. 了解Spring Boot的自动配置

    摘自:https://www.jianshu.com/p/ddb6e32e3faf Spring Boot的自动配置给开发者带来了很大的便利,当开发人员在pom文件中添加starter依赖后,mave ...

  6. Spring Boot的自动配置

    Spring Boot的自动配置 --摘自https://www.hollischuang.com/archives/1791 随着Ruby.Groovy等动态语言的流行,相比较之下Java的开发显得 ...

  7. 初识Spring Boot框架(二)之DIY一个Spring Boot的自动配置

    在上篇博客初识Spring Boot框架中我们初步见识了SpringBoot的方便之处,很多小伙伴可能也会好奇这个Spring Boot是怎么实现自动配置的,那么今天我就带小伙伴我们自己来实现一个简单 ...

  8. spring boot 系列之六:深入理解spring boot的自动配置

    我们知道,spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子: Spring的JdbcTemplate是不是在Classpath里面?如果是,并 ...

  9. Spring Boot 排除自动配置的 4 种方法,关键时刻很有用!

    Spring Boot 提供的自动配置非常强大,某些情况下,自动配置的功能可能不符合我们的需求,需要我们自定义配置,这个时候就需要排除/禁用 Spring Boot 某些类的自动化配置了. 比如:数据 ...

  10. 自定义spring boot的自动配置

    文章目录 添加Maven依赖 创建自定义 Auto-Configuration 添加Class Conditions 添加 bean Conditions Property Conditions Re ...

随机推荐

  1. PTA 有序数组的插入

    6-5 有序数组的插入 (20 分)   本题要求将任一给定元素插入从大到小排好序的数组中合适的位置,以保持结果依然有序. 函数接口定义: bool Insert( List L, ElementTy ...

  2. OpenGL光照计算中法线矩阵原理及推到过程

    问题起源 在计算漫反射关照时,需要用到法线,通过法线和光线的点乘值,计算漫反射的产生的光线强度,所以需要从顶点着色器中将法线数据传递到片源着色器中,但是片源着色器中的顶点坐标是经过了模型矩阵变化过的世 ...

  3. java例题_22 用递归求阶乘 5!

    1 /*22 [程序 22 递归求阶乘] 2 题目:利用递归方法求 5!. 3 程序分析:递归公式:fn!=fn*4! 4 */ 5 6 /*分析 7 * 递归:如果其中每一步都要用到前一步或前几步的 ...

  4. Netflix业务运维分析和总结

    目录 Netflix工作环境的分析和思考 为什么Netflix会做得如此极致? 海量业务规模下的技术架构和挑战 更加合理的组织架构和先进的工具体系及理念 自由与责任并存的企业文化 当前问题: 精选提问 ...

  5. (六)Struts2的拦截器

    一.简介 拦截器体系是struts2重要的组成部分.正是大量的内建拦截器完成了该框架的大部分操作. 比如params拦截器将请求参数解析出来,设置Action的属性.servletConfig拦截器负 ...

  6. ubuntu16.04 安装opencv3.4.0

    参考 https://www.cnblogs.com/arkenstone/p/6490017.html https://blog.csdn.net/u013180339/article/detail ...

  7. 一次使用IDEA中HTTP Client的经历

    1 开端 HTTP Client是IDEA中自带的一个插件,用于代替原来的REST Client,打开Tools->HTTP Client->Test RESTful Web Servic ...

  8. (十三)VMware Harbor 身份验证模式

    VMware Harbor 修改Harbor仓库admin用户 参考:https://blog.csdn.net/qq_40460909 https://blog.csdn.net/qq_404609 ...

  9. Printer Queue UVA - 12100

    The only printer in the computer science students' union is experiencing an extremely heavy workload ...

  10. Day16_89_通过反射机制获取所有构造方法

    通过反射机制获取某个特定的构造方法 * 代码 import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; publ ...