MybatisAutoConfiguration 在装载时会生成SqlSessionTemplate对象托管于容器中,致使MapperFactoryBean中setSqlSessionFactory、setSqlSessionTemplate 前后执行。java
引用javadoc对于 MybatisAutoConfiguration 的描述:
If {@link org.mybatis.spring.annotation.MapperScan} is used, or a configuration file is specified as a property, those will be considered, otherwise this auto-configuration will attempt to register mappers based on the interface definitions in or under the root auto-configuration package. redis若是@MapperScan 或者特定的资源文件被加载,那就要慎重考虑了。此类会尝试注册auto-configuration package路径下定义的接口为mappersspring
而对于MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar在执行过程当中会扫描auto-configuration package路径下带@Mapper注解的类sql
再来比对下MapperScannerConfigurer源码、@MapperScan import的MapperScannerRegistrar的源码,发现与AutoConfiguredMapperScannerRegistrar都是建立了ClassPathMapperScanner并肯定扫描路径!
详见:mybatis的MapperScan到底作了什么mybatis
不使用@MapperScan和@Mapper:app
public interface DecisionAdjustApplyMapper { DecisionAdjustApply selectUnqiue(@Param("applyNo") String applyNo); } import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.ImportResource; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableDiscoveryClient @EnableFeignClients @EnableConfigurationProperties // 配合@ConfigurationProperties 合用 @EnableTransactionManagement(proxyTargetClass = true) // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven /> @EnableAsync //开启异步调用 @EnableAspectJAutoProxy //开启自定义切面 @ImportResource("classpath:dependence.xml") @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class}) //@MapperScan("com.noob.giant.dao") public class GiantApplication { public static void main(String[] args) { //System.setProperty("DEPLOY_ENV", "test"); SpringApplication.run(GiantApplication.class, args); } }
结果: 抛出异常 , Mapper对象没法注入容器! 异步
Description: A component required a bean of type 'com.noob.giant.dao.DecisionAdjustApplyMapper' that could not be found. Action: Consider defining a bean of type 'com.noob.giant.dao.DecisionAdjustApplyMapper' in your configuration. Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.noob.giant.dao.DecisionAdjustApplyMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:518) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:496) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:627) at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:169) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:318) ... 54 common frames omitted
后续测试发现: 单独 在Mapper类上增长@Mapper 或 在启动类上增长“@MapperScan("com.noob.giant.dao")” 都能启动注入成功。ide
在对每个Mapper对象进行beanWrapper过程当中,建立MapperFactoryBean(extends SqlSessionDaoSupport)对象时,会先执行SqlSessionDaoSupport中的setSqlSessionFactory方法根据传入的SqlSessionFactory (测试例子中实际对象是DefaultSqlSessionFactory)建立了一个SqlSessionTemplate,然后再执行setSqlSessionTemplate方法又注入了一个SqlSessionTemplate来覆盖原有的!spring-boot
debug发现:
在AbstractAutowireCapableBeanFactory.populateBean方法中post
入参对象RootBeanDefinition的PropertyValues初始值正常:
处理后最终获得的PropertyValues对象中却含SqlSessionFactory 、SqlSessionTemplate !
在测试中发现,MapperFactoryBean两方法中的SqlSessionTemplate 实例 内容是大体同样(构造方式默认相同:new SqlSessionTemplate(sqlSessionFactory))。 各个不一样Mapper经过setSqlSessionTemplate方法注入的SqlSessionTemplate 实例是同一个对象,且setSqlSessionFactory的入参SqlSessionFactory实例也是同一个对象。
因此推测: sqlSessionTemplate在另外的地方被初始化托管在Spring容器中,被当作自动注入的属性了。
经查验发现:
在运行环境中引用的mybatis-spring-boot-autoconfigure.jar中有一个MybatisAutoConfiguration自动装载了sqlSessionTemplate托管于容器,且提供了扫描@Mapper的处理类AutoConfiguredMapperScannerRegistrar。
<parent>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot</artifactId>
<version>1.3.2</version>
</parent>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
<name>mybatis-spring-boot-autoconfigure</name>
与其余无此jar引用仍旧正常启动且只会执行setSqlSessionFactory方法的项目比对后, 发现没有MybatisAutoConfiguration加载也能启动,但多了个配置:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<property name="basePackage" value="com.noob.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
@Mapper + 启动类排除自动装载: @SpringBootApplication(exclude = { MybatisAutoConfiguration.class })
结果: 失败!没法正常注入Mapper对象,报错同上。
@Mapper + 启动类排除自动装载: @SpringBootApplication(exclude = { MybatisAutoConfiguration.class }) + 启动类增长自动装载@ImportAutoConfiguration(MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class)
@ImportAutoConfiguration(MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class) @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class, RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class }) //@MapperScan({ "com.noob.giant"}) public class GiantApplication { ...
@ImportAutoConfiguration 改成 @Import 就没用了!失败在:AutoConfiguredMapperScannerRegistrar的registerBeanDefinitions时 AutoConfigurationPackages.get()获取不到auto-configuration package 的路径。
结果: 正常启动, 且只执行了setSqlSessionFactory方法!!
debug发现:
后续测试时增入本身显示声明@Bean SqlSessionTemplate,又出现了!
关键在于AbstractAutowireCapableBeanFactory的populateBean方法中执行autowireByType时对于resolveDependency结果是否为空的断定!
详细分析见:https://my.oschina.net/u/3434392/blog/3010046 的【反向验证】
启动类增长@MapperScan({ "com.noob.giant.dao" }) + 启动排除自动装载: @SpringBootApplication(exclude = { MybatisAutoConfiguration.class })
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,MybatisAutoConfiguration.class, RedisAutoConfiguration.class,RedisRepositoriesAutoConfiguration.class }) @MapperScan({ "com.noob.giant.dao", "com.noob.xmagic.dao" }) public class GiantApplication { .......
结果: 具体呈现和第二次一致! 正常启动!!且建立MapperFactoryBean只执行了setSqlSessionFactory方法!!
咱们将启动的Application类移动到“com.noob.giant.hh”路径下 :
(由于启动时默认扫描的是启动类包路径下的对象,因此本测试用例须要在启动Application类从新指定 @SpringBootApplication(scanBasePackages={"com.noob.giant"}, 不然加载不到bean)
@MapperScan({ "com.noob.giant" }):
一个大范围包路径的话,在 ClassPathMapperScanner.doScan 时会把一些额外的接口也看成mapper。 若是精确到Dao层就没有这个现象!
这是由于:
ClassPathBeanDefinitionScanner.doScan方法,在经过basePackage执行findCandidateComponens方法时使用了ClassPathMapperScanner的isCandidateComponent方法对返回结果集断定: 但只是断定bean的定义描述是不是接口。因此致使doScan最终返回的BeanDefinitionHolder集合中含有不少不相干的接口信息。
@Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); }