spring-mybatis 基于springboot整合过
2022-04-25 21:04:11 12 举报
AI智能生成
spring mybatis 整合
作者其他创作
大纲/内容
1.说明
mybatis-spring-boot-starter:2.0.0
spring-boot-starter-parent:2.3.12.RELEASE
2.分析思路
1.必然是要扫描业务Mapper接口
2.那么如何扫描? 熟悉spring(springboot) 原理的 必然知道,一般都是通过后置处理器来完成的
3.要想创建对象,必须在此之前要先去创建<b><font color="#f44336">BeanDefinition</font></b> 对象,那么这就是突破口
1.实现bean定义的接口是:BeanDefinitionRegistryPostProcessor
2.果不其然,可以找到 <b><font color="#f44336">MapperScannerConfigurer</font></b>
1.在纯spring集成mybatis中,我们经常看到这样的配置
2.遗憾的是,我们使用的是 SpringBoot, 所以这个处理器是不工作的
1.看这个也是可以的,也很容易理解工作它的流程
2.他是可配置的,你可以配置是否扫描@Mapper注解,或者只配置包名 都可以的
4.既然在springboot 中 MapperSannerConfigurer 是不工作的,那么怎么扫描我们的业务接口呢?
1.不管是spring 还是 springboot , 在容器启动中,最重要的就是先去创建 bean 定义对象(<b><font color="#f44336">BeanDefinition</font></b> )
2.springboot 中要想使用某一个功能,最先想到的就是其 xxxConfiguration 配置类
<b><font color="#f44336">MybatisAutoConfiguration</font></b>
3.由 MybatisAutoConfiguration 配置类导入必要的组件,完成bean 的定义功能
1.导入了 <b><font color="#f44336">AutoConfiguredMapperScannerRegistrar</font></b>
1.这个类将扫描 <b><font color="#fbc02d">@Mapper</font></b> 注解标注的接口,完成bean定义
2.这个类上面有注释,翻译如下:这将扫描与Spring Boot相同的基本包。如果您想要更强大的功能,您可以显式地使用 <b><font color="#fbc02d">@MapperScan</font></b> 注解
1.不难发现,他扫描的是springboot 主配置类的包,注意:不是@ComponentScan 注解指定的包
2.@MapperScan 注解后面再说
2.注意,这个类要想向容器中注册组件,其前提条件是容器中没有 MapperFactoryBean,这个后面在说
3.<b><font color="#ffffff">AutoConfiguredMapperScannerRegistrar</font></b> 如何完成bean的定义
0.这个类实现了 <b><font color="#000000">ImportBeanDefinitionRegistrar</font></b> 接口,其作用就是给容器注册组件的,我发现自动全注解开发后,这个类经常配合@Import 注解使用
1.springboot 的 bean 定义过程,可以参考我画的图
2.至于这个类是怎么生效的,方法是何时调用的,后面再说,这里先详细讲述它的工作流程(3)
3.AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions
1.首先根据这个类的注释知道,扫描的包路径 默认就是springboot 主配置类的包路径(注意:和@ComponentScan 注解无关)
2.创建了<b><font color="#f44336">ClassPathMapperScanner</font></b> 对象
1.这个类继承了 <font color="#f44336"><b>ClassPathBeanDefinitionScanner</b> </font>类
1.这个类就是spring 扫描 @Componet 注解的,功能性非常强大
这个类在new的时候,其构造器内部会指定默认的扫描规则,就是去扫描@Component 注解
2.这个类继承了 <b><font color="#f44336">ClassPathScanningCandidateComponentProvider</font></b>
1.这个类功能也很强大
2.它可以指定规则或者排除规则,比如spring 指定了扫描 @Componet 注解的类
3.它还可以指定是否符合帅选条件
比如mybatis 指定了只有接口才符合,最终才会创建bean定义对象
3. public Set<BeanDefinitionHolder> <b>doScan</b>(String... basePackages) 这个方法是重中之重
2.添加扫描的过滤规则
1.指定了注解类是 <b><font color="#fbc02d">@Mapper</font></b>
2. new AnnotationTypeFilter(this.annotationClass) , 这个 annotationClass 就是 @Mapper 注解类
3.将上面的过滤器添加到 <font color="#f44336" style="font-weight: bold;">ClassPathScanningCandidateComponentProvider </font><font color="#000000" style="">类中的</font><font color="#f44336" style="font-weight: bold;"> </font>includeFilters 属性中,这样最后面扫描的时候,就只选择 @Mapper 注解的bean作为候选
3.开始扫描
1.调用父类 <font color="#f44336"><b>ClassPathBeanDefinitionScanner</b> </font>类 的 doScan(String... basePackages) 方法
1.扫描这个包下的所有的class文件
2.判断是否为候选的组件
1.遍历排除的组件过滤器:TypeFilter tf : this.excludeFilters
2.遍历包含的组件过滤器:TypeFilter tf : this.includeFilters
1.前面我在这里设置了 @Mapper 注解类
2.如何类上有 @Mapper 注解,表示可以标注为:候选组件
然后创建 <b><font color="#f44336">ScannedGenericBeanDefinition </font></b>对象,然后来到第3步
3.继续判断是否为候选的组件
1.这里调用的方法和上面的方法名字是一样的,只是参数不一样,执行的时机也不一样
2.判断是否为候选组件,比如 mybatis 重写了这个方法,判断类只有是接口才符合候选
如果符合,则将第2步骤创建的 ScannedGenericBeanDefinition 对象保存到 Set 集合中返回
4.遍历 3中返回的 Set<BeanDefinition> 集合,将每个 bean 定义对象保存到 <b><font color="#f44336">BeanDefinitionRegistry</font></b> 对象中,key=beanName(userMapper), value=BeanDefinition(beanClass=com.qiuguan.test.mapper.UserMapper)
2.步骤1执行完成后,返回一个 Set<BeanDefinitionHolder> 集合(就理解为 BeanDefinition 集合就可以)
1.如果集合不为empty, 则开始处理
2.这里有一个最重要的改动,就是获取 BeanDefinition 对象后,将内部的 beanClass 从原来的 com.qiuguan.test.mapper.xxxMapper 接口类变成了现在的 org.mybatis.spring.mapper.MapperFactoryBean, 所以实际上<b><font color="#f44336">BeanDefinitionRegistry</font></b> 对象中,key=beanName(userMapper), value=BeanDefinition, BeanDefinition 对象中保存的原本是业务Mapper, 现在变成了 <b><font color="#f44336">MapperFactoryBean</font></b> 类
为什么这样做?
因为 MapperFactoryBean 是一个 FactoryBean,后面会说到
3.至此扫描 @Mapper 注解就结束了,将扫描到的接口,添加到bean定义中,注册到 <b><font color="#f44336">BeanDefinitionRegistry</font></b> 对象中
学习理解
1.mybaits 的整合让我知道了如果我们自定义包扫描应该怎么处理
继承 <font color="#f44336"><b>ClassPathBeanDefinitionScanner</b> </font>类
2.如何指定候选规则或者排除规则
添加 TypeFilter
3.如何指定最终的候选条件
重写 <font color="#f44336" style="font-weight: bold;">ClassPathScanningCandidateComponentProvider </font><font color="#000000">类中的 </font>isCandidateComponent(AnnotatedBeanDefinition beanDefinition) 方法
这里返回true,才会去创建beanDefinition 对象
4.如果我们也想像mybatis 一样,去扫描自己的包,所以自己的注解,就可以遵照上面的步骤
4.上面3的过程非常具有学习意义,对于我们如何集成spring具有很好的学习经验
顺便说一句
1.springboot 启动时,他会去创建 <b><font color="#f44336">ClassPathBeanDefinitionScanner</font></b>对象,注意,他不是交给spring管理的,他是new出来的
2.它会指定去扫描标注了 @Componet 注解的组件
3.然后mybatis 的 <b><font color="#f44336">ClassPathMapperScanner</font></b> 对象它也是new出来的,由于有继承关系,那么ClassPathBeanDefinitionScanner,ClassPathScanningCandidateComponentProvider 都会去创建新的对象,都是new出来的;通过debug 我发现,像 ClassPathBeanDefinitionScanner 对象会有很多次去创建(就是去 new)
4.@MapperScan 注解
1.我添加到普通配置类 AppConfig 上
1.给容器导入了 <b><font color="#f44336">MapperScannerRegistrar</font></b>
1.实现了 ImportBeanDefinitionRegistrar 接口,其作用就是注册组件
2.MapperScannerRegistrar#registerBeanDefinitions 方法解析
1.和 自动配置类导入的 AutoConfiguredMapperScannerRegistrar 本质时一样的
2. 获取 <b><font color="#fbc02d">@MapperScan</font></b> 注解
1.首先创建 <b><font color="#f44336">ClassPathMapperScanner</font></b> 对象(和前面一样)
2.解析 @MapperScan 注解的属性
1.是否有 <b><font color="#2196f3">annotationClass</font></b> 属性,指定扫描规则时会用到,就是去指定要去扫描的注解,可以为空,如果我想就让它去扫描 @Mapper 注解,那么就在这里指定即可
2.以及其他属性
3.
3.注册扫描或者排除组件的规则
1.如果 <b><font color="#2196f3">annotationClass</font></b> 属性 制定了注解,则就去扫描指定的注解,比如@Mapper, 可以不指定任何东西
2.如果 <b><font color="#2196f3">markerInterface </font></b>属性 指定了接口,则将该接口作为扫描的规则,注意:千万不要指定这个属性
3.如果都没有指定,则该包下的所有类都时符合扫描规则的
4.以上规则,三选一生效
4.开始扫描
1.调用父类 <b><font color="#f44336">ClassPathBeanDefinitionScanner </font></b>的 doScan(String... basePackages) 方法
2.同 <b><font color="#f44336">AutoConfiguredMapperScannerRegistrar</font></b> 的扫描过程一模一样
5.@Mapper 和 @MapperScan 注解如何选择?
1.看到这个是不是很奇怪,为什么是二者如何选择?
1.如果指定了@MapperScan 注解(注意:没有指定 annotationClass 属性),那么 @Mapper 实际上是没用的
2.根本原因是这两个导入的配置类,只有一个会生效
<b>AutoConfiguredMapperScannerRegistrar</b>
通过 mybaits 自动配置类导入的
它生效的前提是没有 MapperFactoryBean
<b>MapperScannerRegistrar</b>
通过 @MapperScan 注解导入的
它将会创建 MapperFactoryBean, 从而导致上面的失效
2.原理探究
1.首先,在我们的自己的配上类上添加 <b><font color="#fbc02d">@MapperScan(basePackages = "com.qiuguan.test.mapper")</font></b> 注解
<b><font color="#f44336">2.其次分析springboot 的 bean 定义过程</font></b>
1.既然是分析bean定义,那么还是以bean 定义处理器为主,找到配置类处理器:<b><font color="#d32f2f">ConfigurationClassPostProcessor</font></b>
1.它继承了 BeanDefinitionRegistryPostProcessor
2.解析配置类组件,将组合条件的bean 保存到 BeanDefinitionRegistry
2. ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
1.这个方法就是在 spring 容器刷新的 invokeBeanFactoryPostProcessors() 方法中调用,遍历所有的处理器,调用方法
2.详细解析这个方法
1.遍历 BeanDefinitionRegistry 所有的 bean definition Names , 此时还不多,只有一些spring自定义的,以及我们的主配置类
2.判断是否为候选的配置类
根据 beanName 获取 BeanDefinition 对象,如果实现这些接口,则不满足成为候选配置类
<b><font color="#f44336">BeanFactoryPostProcessor</font></b>
<b><font color="#f44336">BeanPostProcessor</font></b>
<b><font color="#f44336">AopInfrastructureBean</font></b>
<b><font color="#f44336">EventListenerFactory</font></b>
根据 beanName 获取 BeanDefinition 对象,看是否有 <b><font color="#fbc02d">@Configuration</font></b> 注解,如果有,则添加到Set集合中
3.创建 <b><font color="#f44336">ConfigurationClassParser </font></b>对象(new出来的)用来解析每一个配置类(实际上这里就一个主配置类)
1.解析主要有两个方法
1.ConfigurationClassParser#parse
1.这个方法非常非常复杂,但是这里也会去创建BeanDefiniton 对象,但是只会去创建 <b><font color="#fbc02d">@ComponentScan</font></b> 注解扫描到的,其他的像 @Import 注解导入的,@Bean 导入的,@ImportSource 导入的等等 都不会在这里创建BeanDefinition 对象,而是要下放到步骤2中
2.
2.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
2.解析过程
1.ConfigurationClassParser#parse
0.请先看下 1- 9的总体过程,然后在看每一步骤的细节
1.将当前配置类封装成 <b><font color="#f44336">ConfigurationClass</font></b>类(new ConfigurationClass() ),然后递归地处理配置类及其超类层次结构, do-while 结构
说明,在看下面的内容时,请先看 1-9 的总体内容,细节先不要看,然后1-9 看完了,在从头一点一点看细节
刚开始进来,第一次执行,这个配置类就是主配置类,所以先看下主配置类执行1-9的过程
<b><font color="#d32f2f">ConfigurationClass </font></b>几个重要属性
Set<ConfigurationClass> <font color="#9c27b0">importedBy</font>
当前配置类是由谁导入的
1.图1
2.图2
3.图3
Map<String, Class<? extends BeanDefinitionReader>> <font color="#9c27b0">importedResources</font>
将 <b><font color="#fbc02d">@ImportResource</font></b> 注解导入的xml文件信息 保存到当前配置类中
Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> <font color="#9c27b0">importBeanDefinitionRegistrars</font>
将 <b><font color="#fbc02d">@Import</font></b> 注解导入的实现了 ImportBeanDefinitionRegistrar 接口的类 保存到当前配置类中
Set<BeanMethod> <font color="#9c27b0">beanMethods</font>
将 <font color="#fbc02d"><b>@Bean </b></font>注解标注的方法,保存到当前配置类中
2.判断配置类是否有 <b><font color="#fbc02d">@Component </font></b>注解
<b><font color="#fbc02d">@SpringBootApplication</font></b> —> <b><font color="#fbc02d">@SpringBootConfiguration</font></b> —> <b><font color="#fbc02d">@Configuration</font></b> —> <b><font color="#fbc02d">@Component</font></b> 注解
很明显,主配置类是满足条件的,所以要 递归的处理其内部成员类
1.如果有内部成员类
1.遍历所有的内部类
2.判断内部类是否为候选配置类
1.其作用就是继续执行1-9的步骤
2.成为候选类的条件
标注了 <b><font color="#fbc02d">@Component</font></b> 注解
不难发现 @Component 注解标注的内部类都会成为配置类
@Configuration 注解上也有 @Component
标注了 <b><font color="#fbc02d">@ComponentScan</font></b> 注解
这个不做说明,一般内部类不会标注这个注解
标注了 <b><font color="#fbc02d">@Import</font></b> 注解
标注了 <font color="#fbc02d"><b>@ImportResource</b></font> 注解
或者是类中有标注了<b><font color="#fbc02d">@Bean</font></b> 注解的方法
3.一旦成为候选配置类,则将其添加到List集合中
3.如果这里的候选配置类集合不为空,则遍历每一个候选配置类,然后执行 1-9 中的过程(每次都是 new 新的 ConfigurationClass 对象)
注意:假如内部类标注了<b><font color="#fbc02d">@Component</font></b> 注解,那么在这里也当作是配置类,然后创建 ConfigurationClass 对象进行封装
4.说明:这里如果执行了,说明刚开始的主配置类解析就会停下来,让这些普通的候选配置类先去执行,当这个都执行完了(执行完1-9过程),则继续执行主配置类的第3步骤的解析
2.如果没有内部成员类,则执行接下来的步骤3
3.判断是否有 <b><font color="#fbc02d">@PropertySource</font><font color="#f44336"> </font></b>注解
1.如果有,则解析这个注解,这个注解是引入 自定义的 xxx.properties 是属性文件,添加到环境变量中
2.如果没有,则执行接下来的步骤4
4. 判断是否有 <b><font color="#fbc02d">@ComponentScan</font></b> 注解
1.如果有,则解析这个注解
2.这个其实是一定有的,因为 @SpringBootApplication 注解上标注了,解析的时候,判断basePackages 为空,则使用主配置类所在的包
1.主配置类是 com.qiuguan.spring.MainApplication, 所以 basePackages = "com.qiuguan.spring"
2.通过 ComponentScanAnnotationParser 对象开始解析
1.其内部 会创建 <b><font color="#f44336">ClassPathBeanDefinitionScanner </font></b>对象,这个是spring扫描工作中一个非常重要的对象(mybatis 扫描继承了这个)
在new这个对象时,其构造器内部做了一件很重要的事情:就是指定了扫描规则,就是去扫描@Component 注解
2.解析 @ComponentScan 注解的属性,其中就有,如果basePackages 属性为空,则取当前配置类所在的包为基准包路径
3.调用 <b><font color="#f44336">ClassPathBeanDefinitionScanner </font></b>对象的 doScan(basePackages) 方法开始扫描
1.扫描这个包下的所有的class文件
2.判断是否为候选的组件
1.遍历排除的组件过滤器:TypeFilter tf : this.excludeFilters
2.遍历包含的组件过滤器:TypeFilter tf : this.includeFilters
1.前面在这里设置了 <b><font color="#fbc02d">@Component</font></b> 注解类
2.如何类上有 <b><font color="#fbc02d">@Component</font></b> 注解,表示可以标注为:候选组件
然后创建 <b><font color="#f44336">ScannedGenericBeanDefinition </font></b>对象,然后来到第3步
3.继续判断是否为候选的组件
1.这里调用的方法和上面的方法名字是一样的,只是参数不一样,执行的时机也不一样,上面第一次执行,这里第二次执行
2.判断是否为候选组件,如果标注了<b><font color="#fbc02d">@Component</font></b> 注解,或者是<b><font color="#fbc02d">@Service</font></b>, <b><font color="#fbc02d">@Controller</font></b> , <font color="#fbc02d"><b>@Repository</b> </font>,<font color="#fbc02d"><b>@Configuration</b></font> 注解等,因为这些注解都是基于<b><font color="#fbc02d">@Component</font></b> 注解的
如果符合,则将第2步骤创建的 ScannedGenericBeanDefinition 对象保存到 Set 集合中返回
4.遍历 3中返回的 Set<BeanDefinition> 集合,将每个 bean 定义对象保存到 <b><font color="#f44336">BeanDefinitionRegistry</font></b> 对象中,key=beanName, value=BeanDefinition;然后再将每个BeanDefinition 对象包装成 <b><font color="#f44336">BeanDefinitionHolder</font></b>,放到Set中返回
只要 @ComponentScan 注解扫描到类,才会在这里就保存到 <font color="#000000"><b>BeanDefinitionRegistry</b> </font>对象中,其余的都要放到解析过程2中的 ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
5.遍历 4中返回的 Set<BeanDefinitionHolder> 集合
1.检查是否为候选配置类
2.候选配置类的条件
1.标注了 <b><font color="#fbc02d">@Configuration</font></b> 注解
2.标有基本注解
标注了 <b><font color="#fbc02d">@Component</font></b> 注解
标注了 <b><font color="#fbc02d">@ComponentScan</font></b> 注解
标注了 <b><font color="#fbc02d">@Import</font></b> 注解
标注了 <font color="#fbc02d"><b>@ImportResource</b></font> 注解
3.标有 <b><font color="#fbc02d">@Bean</font></b> 注解的方法
3.如果满足条件,则将当前配置类(这里就这么理解吧,毕竟@Component 注解标注的类在这里都算是配置类)封装到配置类ConfigurationClass 中,然后执行大步骤1中的 do-while 循环,然后遍历每一个类,然后执行1-9步骤
3.解析完成后,则执行接下来的步骤5
5.处理 <b><font color="#fbc02d">@Import</font></b> 注解
1.递归获取当前配置配置类上的<b><font color="#fbc02d">@Import</font></b> 注解,如果没有,则返回,执行步骤6
2.如果有,则遍历所有 @Import 导入的类
1.如果是 <b><font color="#f44336">ImportSelector </font></b>类型的,则处理导入
这个没有太细看,我用的不是很多
2.如果是 <b><font color="#f44336">ImportBeanDefinitionRegistrar </font></b>类型的,则保存到当前配置类 ConfigurationClass (他就是对当前配置类的进一步封装),放到 <font color="#2196f3"><b>importBeanDefinitionRegistrars </b></font>这个Map 属性中, 每一个配置类都会有对应的配置封装类 ConfigurationClass
1.注意:它是保存到当前配置属性中,并没有去扫描去创建 BeanDefinition 对象
2.我一般是在普通配置类上添加了 <font color="#fbc02d"><b>@MapperScan</b></font> 注解,将会导入 <font color="#f44336"><b>MapperScannerRegistrar </b></font>类
然后将 MapperScannerRegistrar 类放到当前配置类的 importBeanDefinitionRegistrars 属性中
当前配置类名字是 ConfigurationClass, 实际上他就是我自定义的普通配置类(AppConfig)的封装, 所以就可以理解为,@Configuration 注解标注的普通业务配置类 AppConfig的 importBeanDefinitionRegistrars 属性中,保存的就是 MapperScannerRegistrar
3.如果1,2都不符合,则将其当作配置类来处理;<b><font color="#2196f3">然后将这个导入的类当作配置类,在去重复1-9 过程</font></b>
3.上面的步骤2执行完,开始下面的步骤6
6.处理 <b><font color="#fbc02d">@ImportResource</font></b> 注解
1.这个注解,就是导入以前spring 的xml文件
2.如果有,则将配置文件信息把保存到配置类ConfigurationClass (他就是对当前配置类的进一步封装)的 <b><font color="#9c27b0">importedResources </font></b>这个Map属性中
3.注意:它是保存到当前配置属性中,并没有去扫描去创建 BeanDefinition 对象
4.接下来执行步骤7
7.解析 <b><font color="#fbc02d">@Bean</font></b> 注解
1.如果当前配置类中有 @Bean 注解标注的方法,则遍历所有的@Bean方法,然后将其放到当前配置类ConfigurationClass (他就是对当前配置类的进一步封装)的 <b><font color="#9c27b0">beanMethods </font></b>这个 Set 属性中,并且会将方法封装成 BeanMethod 对象进行保存
2.注意:他也是保存,并没有去扫描创建 BeanDefinition 对象
3.接下来执行步骤8
8.解析 当前配置类实现的接口
1.看当前配置类是否实现了接口,接口中是否有 <font color="#fbc02d" style=""><b>@Bean </b></font>注解的默认方法,如果有,则解析<b><font color="#fbc02d"> @Bean</font></b>注解,将其放到当前配置类ConfigurationClass 的 <b><font color="#9c27b0">beanMethods </font></b>这个Set属性中
2.注意:他也是保存,并没有去扫描创建 BeanDefinition 对象
3.接下来执行步骤9
9.解析当前类的父类
1.如果有,则直接返回,其实这里的返回就是继续执行 开始的 do-while 循环,在将上面标注的 1-9 执行一遍
2.如果没有,则返回null; 一旦返回 null, 则表示“<font color="#2196f3">当前配置类</font>”解析已经全部完成,当前 do-while 循环退出
3.接下来执行步骤10,不可忽略!!!!
10.整个解析过程结束
1.一旦当前配置类解析过程结束,注意:这个结束表示当前配置类已经执行完了1-9的过程,最后返回了 null(这个结束,只是当前配置类解析结束了,以上1-9的过程实际上是非常复杂的,每一步骤的细节可能仍然谁重复1-9的过程,这样很不好理解,所以我把 1-9 的过程单独拿出来说明,这样比较好理解
2.然后将配置类放到 <b><font color="#f44336">ConfigurationClassParser </font></b>类中的 <b><font color="#9c27b0">configurationClasses </font></b>这个Map属性中,这个Map的key和value 都是前面一直提到的 <b><font color="#f44336">ConfigurationClass </font></b>类,它是每一个配置类的封装,然后这里面保存着1-9中保存进来的内容(绿色部分),然后留着接下来的第二大步骤去进一步解析 (this.reader.loadBeanDefinitions)
1.<b><font color="#9c27b0">configurationClasses </font></b>是一个LinkedHashMap, 保证的存放顺序
2.它具体存储了什么?
1.首先LinkedHashMap 首先存放的是 <b><font color="#fbc02d">@ComponentScan</font></b> 注解扫描到的我们自定义的业务配置类(比如 @Configuration ,@Component 等),以及是 <b><font color="#fbc02d">@Import </font></b>注解导入的类但是这个类没有实现 <b><font color="#d32f2f">ImportSelector</font></b>, <b><font color="#d32f2f">ImportBeanDefinitionRegistrar </font></b>这两个接口。这里有点意思,可能扫描到的一个普通@Component 组件,在这里也会当作配置类保存起来
<b><font color="#f44336">ConfigurationClass[0]</font></b>: beanName 'appConfig', class path resource [com/qiuguan/springboot/config/AppConfig.class]
这个ConfigurationClass 类的 <b><font color="#9c27b0">importBeanDefinitionRegistrars </font></b>属性 中,保存了 <b><font color="#fbc02d">@Import</font></b> 注解导入的 <b><font color="#d32f2f">MapperScannerRegistrar </font></b>类
请参考图片
<b><font color="#f44336">ConfigurationClass[1]</font></b>: beanName 'helloController', class path resource [com/qiuguan/springboot/controller/HelloController.class]
这个就是@Componet 注解标注的(@Controller 本质也是@Component)
<b><font color="#f44336">ConfigurationClass[2]</font></b>: beanName 'null', class path resource [com/qiuguan/springboot/register/SingletonImport.class]
这个类是 <font color="#fbc02d"><b>@Import </b></font>注解导入的,但是这个类没有实现 <b><font color="#d32f2f">ImportSelector</font></b>, <b><font color="#d32f2f">ImportBeanDefinitionRegistrar </font></b>这两个接口
<b><font color="#f44336">ConfigurationClass[3]</font></b>: beanName 'mainApplication', com.qiuguan.springboot.MainApplication
说明:index = 0 和 1 是没有确定顺序的,先解析谁,谁就在第一个位置,<b>但是主配置类,一定是在用户自定义配置类中的最后一个</b>
2.其次存放的就是spring 自动配置导入一些配置类
<b><font color="#f44336">ConfigurationClass[4]</font></b>: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]
<b><font color="#f44336">ConfigurationClass[5]</font></b>: beanName 'null', class path resource [org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration.class]
........
<b><font color="#f44336">ConfigurationClass[28]</font></b>: beanName 'null', class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration$MapperScannerRegistrarNotFoundConfiguration.class]
这个ConfigurationClass 类的 <b><font color="#9c27b0">importBeanDefinitionRegistrars </font></b>属性 中,保存了 <b><font color="#fbc02d">@Import</font></b> 注解导入的 <b><font color="#d32f2f">AutoConfiguredMapperScannerRegistrar </font></b>类
.........
3.所以说,我们用户导入或写的配置类,要比spring自动配置导入的要靠前,越靠前在 this.reader.loadBeanDefinitions 中就先去解析,请看后面的解析过程第2大步骤
2.this.reader.loadBeanDefinitions
1.上面的 parse 方法执行完
1.只会将 <b><font color="#fbc02d">@ComponentScan</font></b> 注解扫描到类以及 @Import 注解导入的且没有实现 <b><font color="#d32f2f">ImportBeanDefinitionRegistrar </font></b>和 <b><font color="#d32f2f">ImportSelector </font></b>接口的类 保存到 <b><font color="#f44336">BeanDefinitionRegistry </font></b>中,其他的不会去创建 BeanDefinition 对象
2.将每个配置类保存到 <b><font color="#f44336">ConfigurationClassParser </font></b>类中的 <b><font color="#9c27b0">configurationClasses </font></b>这个Map属性中
1.这个Map是一个LinkedHashMap, 保证了存放顺序,用于指定的靠前,spring自动导入的靠后
2.这里的配置类,不是在我们习惯意义上的 <b><font color="#fbc02d">@Configuration </font></b>标注的类,而是 <b><font color="#fbc02d">@ComponentScan </font></b>注解扫描到的,比如 <b><font color="#fbc02d">@Component, @Configuration, @Controller, @Service, @Repository</font></b>, 还有 <font color="#fbc02d"><b>@Import</b></font> 注解导入的且没有实现 <b><font color="#d32f2f">ImportBeanDefinitionRegistrar </font></b>和 <b><font color="#d32f2f">ImportSelector </font></b>接口的类; 其他都是放到了配置类的属性中,留着下面再去解析
2.开始解析 <b><font color="#9c27b0">configurationClasses </font></b>属性
1.将 <b><font color="#9c27b0">configurationClasses </font></b>这个LinkedHashMap 属性, 取key 的集合,返回Set<ConfigurationClass>集合,注意:这里返回的也是顺序的(用户指定的在前,spring自动导入的在后)
2.遍历所有的 <b><font color="#d32f2f">ConfigurationClass</font></b>
1.判断是否需要跳过,就是标注了 @Condition 条件
1.如果符合跳过条件,则直接返回,进行下一次的遍历
1.前面说了,配置类是有顺序的,用户引入进来的排在前面,先去解析,即:<b><font color="#fbc02d">@MapperScan</font></b> 注解导入的<b><font color="#d32f2f">MapperScannerRegistrar </font></b>先去解析,首先它在前面先去解析,其次它没有限制条件,所以一定会解析成功
2.当mybatis 的配置类来到这里时,因为<b><font color="#fbc02d">@MapperScan</font></b> 注解导入的 类已经执行过了,有了 <b><font color="#d32f2f">MapperFactoryBean</font></b>, 所以mybatis 的自动配置类导入的 类,因为符合跳过条件,所以就不会解析了,直接返回,执行下一次循环
1.MapperFactoryBean 对象怎么来的?
2.在哪里创建的对象?
创建MapperFactoryBean 对象时,会注册我们的业务Mapper接口,选择的是有参数构造器
2.如果不符合,则执行步骤2
2.解析 <font color="#9c27b0"><b>importedBy </b></font>属性;判断是否是导入的
1.就是前面开始我说的 <b><font color="#d32f2f">ConfigurationClass </font></b>几个重要属性中的 <font color="#9c27b0"><b>importedBy </b></font>属性;请回看
2.我通过 <b><font color="#fbc02d">@Import</font></b> 注解导入的自定义 <b><font color="#d32f2f">SingletonImport </font></b>类(这个类没有继承,没有实现,没有属性和方法),这个类是由AppConfig 导入的,所以它符合条件,于是创建 SingletonImport 类的 <b><font color="#d32f2f">BeanDefinition </font></b>对象,然后保存到 <b><font color="#d32f2f">BeanDefinitionRegistry </font></b>类中
<b><font color="#fbc02d">@Import</font></b> 导入的随便一个类也是要去创建BeanDefinition 对象
3.解析 <font color="#9c27b0"><b>beanMethods </b></font>属性;获取所有的 <font color="#fbc02d">@Bean</font> 注解的方法进行遍历
1.就是前面开始我说的 <b><font color="#d32f2f">ConfigurationClass </font></b>几个重要属性中的 <b><font color="#9c27b0">beanMethods </font></b>属性
2.解析<b><font color="#fbc02d">@Bean</font></b>注解的方法,然后根据返回值创建 <b><font color="#f44336">BeanDefinition </font></b>对象,然后保存到 <b><font color="#f44336">BeanDefinitionRegistry </font></b>中
4.解析 <b><font color="#9c27b0">importedResources </font></b>属性;获取 <font color="#fbc02d">@ImportSource</font> 导入的 xml文件
1.就是前面开始我说的 <b><font color="#d32f2f">ConfigurationClass </font></b>几个重要属性中的 <b><font color="#9c27b0">importedResources </font></b>属性
2.以前没有注解的时候,都是通过spring的xml去创建对象,通过这个注解,就是将xml导入进来,去解析其中的<bean> 标签,然后创建<b><font color="#d32f2f">BeanDefinition </font></b>对象,然后保存到 <b><font color="#d32f2f">BeanDefinitionRegistry </font></b>类中
5.解析 <b><font color="#9c27b0">importBeanDefinitionRegistrars </font></b>属性;获取 <font color="#fbc02d">@Import</font> 导入的实现了 <b><font color="#d32f2f">ImportBeanDefinitionRegistrar </font></b>接口的类
1.就是前面开始我说的 <b><font color="#d32f2f">ConfigurationClass </font></b>几个重要属性中的 <b><font color="#9c27b0">importBeanDefinitionRegistrars </font></b>属性
2.遍历所有实现了<b><font color="#d32f2f">ImportBeanDefinitionRegistrar </font></b>接口的类,然后调用 <b><font color="#2196f3">registerBeanDefinitions </font></b>方法,这个方法内部就是注册组件的逻辑
<b><font color="#fbc02d">1. @MapperScan</font><font color="#ffeb3b"> </font></b>注解注入了 <b><font color="#f44336">MapperScannerRegistrar </font></b>类,这里就要去执行方法,去获取 <b><font color="#fbc02d">@MapperScan</font></b> 注解的信息,去扫描 指定包下的接口(如果没有 <font color="#2196f3">annotationClass </font>属性,那么它默认扫描的就是指定包下的所有类,然后将是接口的类保存到 <b><font color="#d32f2f">BeanDefinition </font></b>对象中
2.前面我说了,ConfigurationClass 是按顺序存储在Map中,用户指定的在前面,先去解析,spring自动导入的在后面,后面解析
<b><font color="#fbc02d"> @MapperScan</font><font color="#ffeb3b"> </font></b>注解注入了 <b><font color="#f44336">MapperScannerRegistrar </font></b>类,在前面,所以他先去解析
mybatis 自动配置类导入的 <b><font color="#d32f2f">AutoConfiguredMapperScannerRegistrar</font></b>, 在后面,它后面再去解析
但是当他去解析的时候,第一步会因为有跳过条件,所以它不会在去执行方法了,直接退出,进行下一次循环
3.在<b><font color="#fbc02d">@MapperScan</font></b> 执行完成后,会存在一个 <b><font color="#d32f2f">MapperFactoryBean </font></b>对象,mybatis 自动配置类的 AutoConfiguredMapperScannerRegistrar 有一个生效条件就是 <font color="#fbc02d"><b>@ConditionalOnMissingBean(MapperFactoryBean.class)</b></font><font color="#616161">; </font><font color="#212121">所以 mybatis 自动配置类,在执行第1步骤的时候,会因为符合跳过条件,从而不进行解析</font>
这个MapperFactoryBean对象
3.结论
1.实际上 @MapperScan 注解扫描的并不是 @Mapper 注解
2.如果使用了@MapperScan 注解,只要指定好包名,那么@Mapper注解就可以忽略了
如果就想让@MapperScan 注解去扫描@Mapper注解,则在属性 annotationClass 指定为 @Mapper 类名
3.如果只使用@Mapper, 那么它使用的是 springboot 启动类 的包,将会遍历所有的类,然后将接口的保存起来,所以实际上它会延长启动时间,而且可能存在误判(某些并不是Mapper接口,可能就是普通的其他业务接口)
4.所以:最好使用@MapperScan 注解
6.如何解析配置文件和Mapper接口?
1.前面讲述了 spring 如何 集成 mybatis 扫描 Mapper 接口,从而创建 BeanDefinition 对象,等待后面去创建对象
2.接下里就是创建对象
1.如何创建对象,创建哪个对象?
<b><font color="#d32f2f">SqlSessionFactory</font></b>
2.应该从哪里分析
1.既然是springboot, 那么首先要看的自动配置类--<b><font color="#d32f2f">MybatisAutoConfiguration</font></b>
2.自动配置类中有一个 <font color="#e65100">sqlSessionFactory()</font> 方法,就是去创建 SqlSessionFactory 对象,那么就从这里分析
1.内部首先创建 <b><font color="#d32f2f">SqlSessionFactoryBean </font></b>对象(new出来的)
<b><font color="#f57f17">implements </font><font color="#388e3c">FactoryBean<SqlSessionFactory></font></b>
是一个工厂Bean
2.以前纯spring集成mybatis, 都是在spring 配置文件中,指定 mybatis 的全局配置文件和 Mapper的xml文件位置,而现在都是通过 springboot 的 yml 或者properties 文件去指定
3.给 <b><font color="#d32f2f">SqlSessionFactoryBean</font></b> 设置属性
1.从springboot 的 属性文件中获取mybatis 的全局配置文件位置
<font color="#ff9800">可以没有</font>
2.创建 <b><font color="#d32f2f">Configuration </font></b>对象,保存到属性中,这个 Configuration 对象是mybayis 中一个重量级的配置类
3.获取指定的 mapper接口的xml配置文件位置
<font color="#ff9800">可以没有</font>
4.以及一些其他配置,比如别名等等,这些不重要
4.然后调用 <b style="color: rgb(211, 47, 47);">SqlSessionFactoryBean</b><font color="#212121"> 对象的 </font><b style=""><font color="#9c27b0">getObject()</font></b><font color="#212121"> 方法</font>
1.解析 mybatis 的 <b><font color="#7b1fa2">configLocation </font></b>属性(基于springboot 进行配置的)
1.这个属性,就是mybatis 的全局配置文件
2.如果配置了,则解析
1.开始解析标签,比如 <font color="#388e3c"><properties> , <environments>, <mappers></font> 等等<br>
2.重点说 <b><font color="#388e3c"><mappers></font></b> 标签
1.如果mybatis 全局配置文件中没有配置 <mappers> 标签,则直接跳过;或者有 <mappers> 标签但是没有 <mapper>标签也同样跳过
2.如果配置了<mappers> 标签,并且里面有 <mapper> 标签,则遍历解析每个 <mapper> 标签
1.例如:<font color="#388e3c"><mapper resource="mapper/UserMapper.xml"/></font>
不要加 classpath: 不然会报错找不到这个文件
2.首先判断这个资源是否加载过了,按照资源名resource="mapper/UserMapper.xml" 判断
3.如果没有加载过,则开始解析
1.先解析 <mapper> 标签,获取 namespace, 这个就是接口的全类名
2.在解析 <mapper> 标签下的一些独立标签,比如
/mapper/sql
/mapper/resultMap
3.在解析 <mapper> 标签下的 CRUD 标签
1.将标签的所有信息,包括sql 全部封装到 <b><font color="#d32f2f">MappedStatement </font></b>对象中
一个标签对应一个 <b><font color="#d32f2f">MappedStatement </font></b>对象
2.然后将 <b><font color="#d32f2f">MappedStatement </font></b>对象保存到 <b><font color="#d32f2f">Configuration </font></b>对象的 <b><font color="#7b1fa2">mappedStatements </font></b>属性中(是一个Map)
1.key 有两个,一个是namespace.methodName, value 就是 <b><font color="#d32f2f">MappedStatement </font></b>对象
2.另一个key 就是 methodName, value <b><font color="#d32f2f">MappedStatement </font></b>还是
4.将上面解析完的资源保存到 <b><font color="#d32f2f">Configuration </font></b>类的 <b><font color="#7b1fa2">loadedResources </font></b>属性,与步骤2换成一对
值就是 mapper/UserMapper.xml
5.将 Mapper 接口注册到 <b><font color="#d32f2f">Configuration</font></b> 类的 <b><font color="#7b1fa2">mapperRegistry </font></b>属性中<br>
1.根据 namespace, 利用反射获取 Mapper 接口的 Class 对象
2.如果该接口没有注册过,则调用 <b><font color="#d32f2f">Configuration</font></b> 对象的 <b><font color="#f57c00">addMapper </font></b>方法
内部是调用 <b><font color="#d32f2f">MapperRegistry </font></b>对象的 <b><font color="#f57c00">addMapper </font></b>方法
1.如果不是接口,则直接跳出,不进行注册<br>
2.将当前接口保存到 <b><font color="#d32f2f">MapperRegistry </font></b>对象的 <b><font color="#7b1fa2">knownMappers </font></b>的Map属性中<br>
key是接口Class对象
value 是 <b><font color="#d32f2f">MapperProxyFactory </font></b>对象
<b><font color="#4caf50">knownMappers.put(type, new MapperProxyFactory<>(type))</font></b>
3.我觉得执行完上面的步骤2就结束了,但是其实没有,它创建了 <b><font color="#d32f2f">MapperAnnotationBuilder </font></b>对象
1.他是去解析接口的注解信息
2.它和解析xml是一样的,也会创建 <b><font color="#d32f2f">MappedStatement </font></b>对象,并将注册到 <b><font color="#d32f2f">Configuration</font></b> 类的 <b><font color="#7b1fa2">mapperRegistry </font></b>属性中
注意:如果xml和注解重复了,那么这里就会报错,启动失败
3.将1,2 解析出来的各个信息都封装到 <b><font color="#d32f2f">Configuration </font></b>对象中
3.如果没有配置,则执行第2步
2.解析 mybatis 的 <b><font color="#7b1fa2">mapperLocations </font></b>属性(基于springboot进行配置的)
1.这个就是我们的业务 Mapper xml文件
2.如果配置了,则解析
1.首先它执行的 1.2.2.2.2 资源加载判断
基于mybaits 全局配置的解析出来的是: mapper/UserMapper.xml
基于springboot 全局配置解析出来的是:[mapper/UserMapper.xml]
所以,虽然是同一个文件,但是判断出现了问题,所以还是会去解析
2.解析 Mapper.xml 文件
1.如果mybatis 全局配置文件中也配置了,就是我上面说的情况,这里同样会去解析
解析过程同1.2.2.2.3 一样
但是当保存 <b><font color="#d32f2f">MappedStatement </font></b>对象时,由于key已经存在了,所以报错
2.所以,1.注解,2.mybatis 全局配置文件制定 mapper 接口,3.springboot 制定mapper 接口,三者要选一,不然报错
3.如果没有配置,则执行步骤3
3.最终就是返回一个 <b><font color="#d32f2f">SqlSessionFactory</font></b>
<b><font color="#d32f2f">DefaultSqlSessionFactory</font></b>
4.如果既没有没有在springboot中配置,也没有mybatis 全局配置文件中配置,而是写的注解SQL, 请问何时解析 sql, 把偶才能 MappendStatement 对象,注册 Mapper 接口呢?
重点关注 <b><font color="#d32f2f">MapperFactoryBean </font></b>对象的初始化
checkDaoConfig()
关注第7章节
3.自动配置类中有一个<font color="#e65100"> sqlSessionTemplate()</font> 方法,就是去创建 <b><font color="#d32f2f">SqlSessionTemplate </font></b>对象,继续分析
1直接 new SqlSessionTemplate() 创建对象
这个对象内部有个 sqlSessionProxy 属性,是 SqlSession 代理对象,这里用到了代理
3.mybatis 里面的重要的两个工厂对象
<b><font color="#d32f2f">SqlSessionFactoryBean</font></b>
<font color="#fbc02d">implements </font><b><font color="#d32f2f">FactoryBean<SqlSessionFactory></font></b>
<b><font color="#d32f2f">MapperFactoryBean</font></b>
<font color="#fbc02d">implements </font><font color="#d32f2f" style="font-weight: bold;">FactoryBean<T></font>
7.如何工作
1.先说一个问题,如果我没有任何的xml配置,只有纯注解sql, 如何解析?
1.前面说bean 定义的过程时,可以知道,<b><font color="#d32f2f">DefaultListableBeanFactory </font></b>容器中的 <b><font color="#7b1fa2">beanDefinitionMap </font></b>属性,保存了的bean 定义信息
其中就有UserMapper
key = userMapper
value = <b><font color="#d32f2f">GenericBeanDefinition</font></b>; 其中 GenericBeanDefinition 的 beanClass 属性修改成了 MapperFactoryBean.class
2.熟悉spring的知道,有了bean定义信息后,才可以创建对象,那么这里来创建UserMapper 对象
1.根据beanName(userMapper) 获取 BeanDefiniton 对象,由于是工厂对象,所以执行getBean() 逻辑
工厂对象实际上在之前就已经创建了,只不过是存在 <b><font color="#d32f2f">AbstractAutowireCapableBeanFactory </font></b>类的 <b><font color="#7b1fa2">factoryBeanInstanceCache</font></b>属性中
创建 <b><font color="#d32f2f">MapperFactoryBean </font></b>这个工厂对象时,使用的是有参数构造器,参数就是我们的业务Mapper接口
2.执行工厂对象的初始化方法
1.MapperFactoryBean#checkDaoConfig() 在父类的初始化方法中进行调用的
2.在创建<b><font color="#d32f2f">MapperFactoryBean </font></b>对象时,使用的是有参数构造器(反射),参数就是我们的UserMapper 接口
3.注册Mapper
1.将Mapper接口保存到 <b><font color="#d32f2f">MapperRegistry </font></b>对象的 <b><font color="#7b1fa2">knownMappers </font></b>属性中
key = UserMapper.class
value = MapperProxyFactory
knownMappers.put(type, new MapperProxyFactory<>(type));
2. 创建 <b><font color="#d32f2f">MapperAnnotationBuilder </font></b>对象,解析接口以及接口的注解标注的方法,将每一个方法的信息(就类似xml的标签)封装到 <b><font color="#d32f2f">MappedStatement</font></b>对象中
3.不管是哪个声明sql的形式,都是在注册Mapper接口时去解析sql信息的
2.请求是如何工作的?
1.首先注入进来的是 MapperProxy 对象
为什么呢?
但是容器中保存的value 明明是 MapperFactoryBean 对象,为什么注入的时候变成了 MapperProxy 呢?
这是因为工厂对象在注入时,会调用工厂对象的 getObject(), 这个方法返回的对象才是将要注入的对象
2.那么当调用UserMapper#selectById 时如何工作?
1.前面说了注入 <b><font color="#d32f2f">MapperFactoryBean </font></b>这个工厂对象时,实际注入的是 MapperFactoryBean#getObject() 方法,实际注入的是 <b><font color="#d32f2f">MapperProxy </font></b>对象
在创建这个对象时,接口,SqlSession, Configuration 对象都会传入进来的
2.调用时将会来到 MapperProxy#invoke() 方法
进而由 <b><font color="#d32f2f">MapperMethod </font></b>对象调用
进而由 <b><font color="#d32f2f">SqlSessionTemplate </font></b>对象调用
由 里面的 <b><font color="#d32f2f">SqlSessionTemplate </font></b>代理对象 sqlSessionProxy 进行调用
来到 <b><font color="#d32f2f">SqlSessionInterceptor </font></b>的 invoke() 方法中
1. 通过 <b><font color="#d32f2f">SqlSessionFactory </font></b>对象打开一个 <b><font color="#d32f2f">DefaultSqlSession </font></b>对象
2.然后由 <b><font color="#d32f2f">DefaultSqlSession </font></b>执行CRUD
后面的步骤就很清晰了,可以参考我画的mybatis 流程图
0 条评论
下一页