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(); }