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