最近一直处于疯狂读源码的状态,简直就是少壮不努力,老大徒伤悲。言归正传,虽然所在的公司用的框架都是内部的,基于开源的框架封装的,但是平时自己写东西的时候,还是喜欢直接用开源的框架。在大学的时候就用过SpringBoot,工作以后也用过Mybatis,但是会用却不是很了解原理。最近就很好奇,SpringBoot的项目通过starter的形式集成Mybatis后,启动的流程与原理,于是就好好的梳理了一下。

开始介绍SpringBoot 集成 Mybatis 启动流程与原理

    首先,先介绍下笔者的示例项目的信息:

  •      springBoot版本:2.2.0.RELEASE
  •      mybatis版本:3.5.2

    通过starter引入

<dependency>

    <groupId>org.mybatis.spring.boot</groupId>

    <artifactId>mybatis-spring-boot-starter</artifactId>

    <version>2.1.0</version>

</dependency>

  项目中,只有一个Mapper接口,如下,只有一个查询接口。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_spring

启动类上,没有关于Mybatis的注解,也没有自定义的Mybatis的配置类,配置文件中也没有特殊的配置。接下来开始描述流程:

另外说明:最近想把项目用开源的框架再撸一遍,因此一些内容可能是公司项目的命名,因此都马赛克了,以后有空换个简单的项目把截图再替换下吧。

1. Mybatis starter 通过SPI扩展机制实现的自动装配

找到对应的Spring.factories

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Mybatis_02

spring.factories的配置如下:

# Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

具体看MybatisAutoConfiguration这个配置类 

2. 实例化sqlSessionFactory和sqlSessionTemplate

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Java_03

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Mybatis_04

其实大家都知道 对应Mapper的接口,实现原理其实是代理,但是具体流程是怎么实现的呢 

3.在Springboot启动,刷新应用上下文时,即执行SpringApplication的refreshContext方法

请锁定PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法中的这段代码

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Mybatis_05

查看变量postProcessorNames的内容

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Mybatis_06

存在MapperScannerConfigurer

查看MapperScannerConfigurer类,一开始很疑惑,因为在这个类上并没有@Component注解。那是怎么注册到BeanDefinition中的呢,后来发现了如下代码,将MapperScannerConfigurer注册到BeanDefinition中:

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Mybatis_07

言归正传,来看看如何实现扫描的 

4.注解扫描

因为MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,因此在refresh时,会调用postProcessBeanDefinitionRegistry方法。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_实例化_08

一步步往里看,到ClassPathBeanDefinitionScanner类中doScan方法,findCandidateComponents是去扫描指定的包中@Mapper的接口。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Mybatis_09

从这里可以看出,已经搜索到@Mapper注解的接口了。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_SpringBoot_10

但是看这个definitionHolder,总觉得有点奇怪

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_spring_11

ClassPathMapperScanner类中,扫描完,beanDefinitions中存放着扫描到的所有的mapper的接口,然后执行processBeanDefinitions方法。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Java_12

在processBeanDefinitions方法中,会对刚刚的beanDefinition进行进一步加工,这也解决了我之前的疑惑。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Mybatis_13

再看看beandefinition

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Java_14

 

5.如何实例化代理类

由于实例化容器是一大堆关于spring的逻辑,因此粗粗略过。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_实例化_15

看DefaultListableBeanFactory的resolveDependency方法,顾名思义,应该是解决依赖的方法,因为serviceImpl类中,成员变量xxxDao是以autowrie的形式注入进来的,因此需要实例化xxxDao进行注入。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_spring_16

跟着代码一步步追进去,就能发现AbstractBeanFactory的doGetBean中,以下方法,getSingleton,会先判断该单例的bean是否已经被创建,如果没有,则创建

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_spring_17SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_spring_17

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_实例化_19

最终会调用MapperFacctoryBean的getObject方法中,获取代理类

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_SpringBoot_20

这样在使用的时候,获取的便是代理类。

另外,当多个xxxServiceImpl依赖同一个@Mapper注解的接口时,如果该接口已经实例化过后,便可以从以下方法中获取之前实例化的bean,且代理类是同一个对象。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_Java_21

在第一次创建时,便会将实例的对象放在factoryBeanObjectCache中。

SpringBoot mybatis执行sql输出 springboot mybatis原理执行流程_实例化_22

 

  最后

      追了上面的代码以后,也算大致了解许多SpringBoot的Starter是怎么集成的,融会贯通。