一.从开发中遇到的问题开始
问题描述 : 在一个springboot+mybatis的项目中,在dao也就是Mapper接口上配置了@Mapper注解,其他同事在启动类还配置了@MapperScan注解(包扫描没有配全面),进行批量指定所生成的Mapper接口动态代理接口类,所以开始的时候没有在@MapperScan直接我新建的dao包,就报错,但是有@Mapper注解。
No qualifying bean of type 'com.xxx.mapper.xxxMapper'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations:{@org.springframework.beans
.factory.annotation.Autowired(required=true)}
1、只使用@Mapper注解,不使用@MapperScan注解。会扫描@Mapper注解所在接口,生成动态代理类,注入到Spring容器中。
2、只使用@MapperScan注解,不使用@Mapper注解。会扫描@MapperScan注解配置的包下面的接口生成动态代理类,注入到Spring容器中。
3、@Mapper、@MapperScan注解都使用,@Mapper接口,在@MapperScan注解中有配置包路径,那么可以正常使用。
4、@Mapper、@MapperScan注解都使用,@Mapper接口,在@MapperScan注解中没有配置包路径,那么会报错,解决办法,就是在@MapperScan注解中配置正确路径下的包即可。
二.@Mapper、@MapperScan注解解释
@Mapperscan:标注在 springboot 的启动类上面,配置 basePackages 属性,可以去扫描指定路径下的接口扫描为 Mapper 接口。
@Mapper:标注在接口上,表明这是一个 Mapper 接口。
工作原理:两者都使普通接口转为 mapper 接口,也即是把接口的beanClass设置为mapperFactoryBean
三.源码分析
1.@MapperScan注解扫描分析
可以发现 @Mapperscan注解类中包含有注解@Import(MapperScannerRegistrar.class)
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar ,那么在启动时spring容器会调用并执行 registerBeanDefinitions() 方法,扫描对应路径上(@MapperScan注解上带的basepackages)的类扫描到spring容器中.
在这个方法中重点看这行代码:
代码中会实例化 MapperScannerConfigurer,这个类实现了BeanDefinitionRegistryPostProcessor,会容器启动时调用postProcessBeanDefinitionRegistry() 方法,在这方法中设置了接口的 beanClass 。
ClassPathMapperScanner 重写了doScan方法,主要是扫描路径,并将扫描的信息转为beanDefinition,设置其为MapperFactoryBean,最终将扫描好并封装为MapperFactoryBean的类加入到ioc容器中
mapperFactoryBean重写了getObejct()方法。
跟踪 getObject() 方法,发现最终实例化接口的代码如下:
也即是我们写 mapper 接口,然后 mybatis 为我们生成一个 MapperProxy 对象去实现 mapper 接口。
1.1@MapperSca总结
@MapperScan 实际做的事情:
1.扫描指定路径,并将路径下的信息记录为BeanDefinition;
2.将获取的BeanDefinition,设置为MapperFactoryBean,注入IOC;
2.@Mapper注解扫描分析
@Mapper 注解是在 mybatis加载时候起作用的,在 MybatisAutoConfiguration 中:
如果当前IOC容器没有MapperFactoryBean.class, MapperScannerConfigurer.class这两个bean,则会执行如下代码
由该类的头部注解可知,在 spring 上下文中没有 MapperScannerConfigurer 实例时候会进行对@Mapper注解类的初始化。由此可知 @Mapper和@Mapperscan只有一个起作用,而 @Mapperscan 优先级较高。因此当 @Mapperscan 不存在时候,MybatisAutoConfiguration 该类的头部注解@import,会实例化 AutoConfiguredMapperScannerRegistrar ,这个类 会调用registerBeanDefinitions方法,将MapperScannerConfigurer这个类注入到了IOC容器中。
可以看到,和之前@MapperScan对应上了,都是注册了MapperScannerConfigurer,也就是两种注解方式都是通过MapperScannerConfigurer扫描mapper注册的
下面我们仔细翻一下源码,看下MapperScannerConfigurer到底怎么处理@MapperScan和@Mapper
registerFilters方法指定了是走@Mapper注解扫描,还是@MapperScan包扫描
public void registerFilters() {
boolean acceptAllInterfaces = true;
// 如果指定了扫描类型(@Mapper走这里)
// annotationClass在前面的AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions被注入
// 就是这段builder.addPropertyValue("annotationClass", Mapper.class);
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
......
// 如果没指定扫描类型,则扫描全部(@MapperScan走这里)
if (acceptAllInterfaces) {
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
四.@MapperScan和@Mapper的使用建议
mapper接口所在包比较集中式可以在启动类上加@MapperScan注解指定mapper接口所在的包路径。
如果mapper接口所在的包路径比较分散(多部门,多人开发),建议直接在接口上加@Mapper注解,去掉启动类上的@MapperScan包扫描