问题:
SpringBoot中没有配置Mybatis的整合类,只有Mybatis的配置文件,那么是怎么进行关联的 ?
在Mapper接口上不加@component或者是@repository也能将Mapper注入到Spring中,是如何实现的?
所写的Mapper接口是如何关联到xml里面所写的sql语句的?
如果对于上述三个问题的答案不是很清楚的话,可以往下面,本文将从源码的角度分析SpringBoot与Mybatis整合的源码,对于Spring和Mybatis的整合思想都是一样的,因为现在公司中大多数使用的是SpringBoot,所以选用SpringBoot与Mybatis整合作为分析的重点。
SpringBoot与Mybatis整合体现在代码上是很简单,只需要三步就行:
一.引入Mybatis启动包:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.springboot.version}</version>
</dependency>
二.在SpringBoot的启动类上面@MapperScan注解:
@MapperScan(value = "it.cast.wechat.mapper")
@SpringBootApplication
public class WechatApplication {
public static void main(String[] args) {
SpringApplication.run(WechatApplication.class, args);
}
}
三:在SpringBoot的application.properties配置文件中指定mybatis的配置文件和mapper的配置文件的路径:
mybatis.config-location = classpath:mybatis/SqlMapConfig.xml
mybatis.mapper-locations = classpath:mybatis/mapper/*.xml
这样就指定Mapper接口、Mapper的xml文件和Mybatis配置文件的路径,这样就可以读取到文件,并进行解析了。
下面就根据SpringBoot的启动流程进行解析
源码解析:
1.启动类
@MapperScan(value = "it.cast.wechat.mapper")
@SpringBootApplication
public class WechatApplication {
public static void main(String[] args) {
//1.传入自身的class类,启动SpringBoot项目
SpringApplication.run(WechatApplication.class, args);
}
}
首先将自身的class类传入进行然后进行启动,这里牵扯到SpringBoot的启动流程,就不展开讲解了,说一下主要内容,其会在WechatApplication 变成一个BeanDefinition,然后放入到BeanFactory的BeanDefinitionNames集合中,然后在Spring的refresh方法中执行调用bean工厂的后置处理器时,将遍历BeanDefinitionNames集合,然后会找到包裹WechatApplication类的BeanDefinition,此时会解析其类中的注解信息,然后就可以买到@MapperScan注解了。其参考:Spring中bean的生命周期(最详细),而解析的关键类ConfigurationClassPostProcessor可以自行百度,以后有时间会分析一下这个类的源码。
2.@MapperScan注解
@MapperScan注解,会使用@Import+ImportBeanDefinitionRegistrar接口的方法向容器中注册一个bean,关于@Import的源码分析可以参考:Spring中@Import注解源码分析,关于@MapperScan注解可以查看下面代码,仅给出重要的属性。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
3.MapperScannerRegistrar类
MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以可以使用@Import来向Spring容器中导出BeanDefinition。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//1.获取@MapperScan注解属性信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
//2.使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
//3.从@MapperScan注解属性中获取设置的值,然后设置给MapperScannerConfigurer的bd
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
//忽略其他设置属性的代码
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
//获取@MapperScan注解的属性值,然后添加到basePackages集合中
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
//给BeanDefinition设置属性
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
//4.注册该MapperScannerConfigurer的bd
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
上面可以总结为四步:
1.获取@MapperScan注解上面的属性信息,因为@Import(MapperScannerRegistrar.class)是标注在@MapperScan上面的,所以第一步应该先获取@MapperScan注解的属性信息,这些属性信息使我们手动设置的,在WechatApplication类上使用@MapperScan,是这样使用的@MapperScan(value = "it.cast.wechat.mapper")。
2.使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象,对于Spring整合Mybatis熟悉的同学应该都知道,需要在Spring的XML文件中配置MapperScannerConfigurer对象,对于SpringBoot来说,这些都给你屏蔽了,不过其实现都是一样的。
3.将@MapperScan注解属性中的值,设置到MapperScannerConfigurer的BeanDefinition对象上面,这是我们就可以拿到了在@MapperScan设置的value等值
4.使用builder创建BeanDefinition,并放入Spring容器中。
4.MapperScannerConfigurer类
由上图可知其MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,我们知道BeanDefinitionRegistryPostProcessor接口是Spring中的一个扩展点,是的作用就是往Spring中注入BeanDefinition的,在Spring的refresh方法中执行调用bean工厂的后置处理器时进行执行。
这里Spring在调用BeanDefinitionRegistryPostProcessor时,会调用三次,其调用先后顺序是这样的:
1.如果类实现了BeanDefinitionRegistryPostProcessor,并且实现了PriorityOrdered
2.如果类实现了BeanDefinitionRegistryPostProcessor,并且实现了Ordered
3.如果类只实现了BeanDefinitionRegistryPostProcessor
扫描WechatApplication类是在ConfigurationClassPostProcessor类中的postProcessBeanDefinitionRegistry方法进行的,其实现了BeanDefinitionRegistryPostProcessor,并且实现了PriorityOrdered,在扫描WechatApplication类是,会扫描其身上的注解信息,然后进行解析进行扫描类,此时扫描到@MapperScan注解,向Spring容器中注入MapperScannerConfigurer,然后当ConfigurationClassPostProcessor扫描完之后,接着会调用MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法,进行扫描接口。
5.MapperScannerConfigurer类的postProcessBeanDefinitionRegistry方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//1.构建一个ClassPathMapperScanner,并填充相应属性
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.valu