相信大家在使用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. Android Studio 之 通过 Intent 完成点击按钮实现页面跳转

    •Intent 简介 Intent 是 Android 程序中各组件之间进行交互的一种重要方式: 它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据. Intent 有多个构造函数,其 ...

  2. java 基础知识储备

    初始JAVA JAVA 帝国的诞生 1972年C诞生 贴近硬件,运行极快,效率极高. 操作系统,编译器,数据库,网络系统等 指针和内存管理 1982年C++诞生 面向对象 兼容C 图形领域.游戏等 纵 ...

  3. [状压DP]车

    车 车 车 题目描述 在 n ∗ n n*n n∗n( n ≤ 20 n≤20 n≤20)的方格棋盘上放置 n n n个车(可以攻击所在行.列),有些格子不能放,求使它们不能互相攻击的方案总数. 输入 ...

  4. python工业互联网应用实战11—客户端UI

    这个章节我们将演示用户端界面的开发,当前演示界面还是采用先实现基本功能再逐步完善的"敏捷"模式.首先聚焦在功能逻辑方面实现普通用户与系统的交互,普通用户通过url能查看到当前任务的 ...

  5. HTML(〇):简介导读

    网页 什么是网页 网站(Website):是指在因特网上根据一定的规则,使用HTML(标准通用标记语言)等工具制作的用于展示特定内容相关网页的集合. 网页(webpage):是网站中的一页,通常是HT ...

  6. OO_Unit2 关于性能优化与测试的那些事

    OO_Unit2 关于性能优化与测试的那些事 OO的第2单元到本周也就正式完结了.尽管这个单元的主旋律是多线程,但"面向对象"的基本思想仍然是我们一切架构与优化的出发点与前提.因此 ...

  7. (七)docker-compose 安装

    一.Docker-Compose简介 Docker-Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排. Docker-Compose将所管理的容器分为三层,分别 ...

  8. 通过Dapr实现一个简单的基于.net的微服务电商系统(六)——一步一步教你如何撸Dapr之Actor服务

    我个人认为Actor应该是Dapr里比较重头的部分也是Dapr一直在讲的所谓"stateful applications"真正具体的一个实现(个人认为),上一章讲到有状态服务可能很 ...

  9. 《C++编程思想》部分章节学习笔记整理

    简介 此笔记为<C++编程思想>中部分章节的学习笔记,主要是第15章--多态性和虚函数 的学习笔记,此外还有少量其他章节的内容. 目录 文档:<C++编程思想>

  10. M1 和 Docker 谈了个恋爱

    出于开源项目的需要,我准备把之前在 windows 下运行的开源项目移植到 Mac 上跑得试下,但是 Mac M1 芯片并不能很好地支持 Docker,这不,发现 Docker 也正式支持 Mac 了 ...