Mybatis的Mapper掃描機(jī)制:@MapperScan源碼(轉(zhuǎn))
原文:https://blog.csdn.net/qq_42187215/article/details/132650985
作者:Zzzj_1233
來源:CSDN
1. 如何基于包掃描類
首先提出一個(gè)問題:如通過給定包名來掃描包中類的信息呢?
方式一:使用Class.forName
首先,通過包名訪問目錄下的所有class文件。
接下來,將所有class文件的路徑轉(zhuǎn)換為類路徑格式,例如將 “a/b/c/HelloWorld.class” 轉(zhuǎn)換為 “a.b.c.HelloWorld”。
最后,使用 Class.forName 方法加載 a.b.c.HelloWorld。
方式二:基于字節(jié)碼庫
同樣,首先通過包名訪問目錄下的所有class文件。
然后,通過字節(jié)碼庫讀取這些class文件,以獲取類信息。
Spring采取的是方式二
并且使用ASM作為字節(jié)碼庫, ( ASM是java字節(jié)碼類庫中性能最高的之一 )
那為什么Spring不采用Class.forName呢
使用Class.forName必定會(huì)經(jīng)歷查找 -> 加載 -> 初始化的過程,最終將類加載到JVM中
可是我們指定一個(gè)包去掃描,只需要掃描出包含特定注解的類,而并不需要將所有的類都加載到JVM中
2. @MapperScan的用處
在傳統(tǒng)的Mybatis開發(fā)中
通過SqlSession#getMapper來得到Mapper
然后使用Mapper進(jìn)行數(shù)據(jù)庫訪問
而在Mybatis集成Spring的開發(fā)中,只需要給配置類上標(biāo)注@MappperScan("指定包名")即可將Mapper注入到Spring的容器中
例如:
@SpringBootApplication @MapperScan("com.zzzj.dao") public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args) } }
2. 概要
@MapperScan和@ComponentScan一樣,都是基于ClassPathBeanDefinitionScanner實(shí)現(xiàn)的
只不過@MapperScan做了一些額外的處理,將掃描出來的BeanDefinition替換了,來了一手貍貓換太子
如果還不熟悉ClassPathBeanDefinitionScanner
可以查看這篇文章:Spring的Bean掃描機(jī)制:ClassPathBeanDefinitionScanner源碼
3. 源碼追蹤
3.1 @Import(MapperScannerRegistrar.class)
@MapperScan注解上使用@Import注解導(dǎo)入了MapperScannerRegistrar
@Import(MapperScannerRegistrar.class) public @interface MapperScan { // ... }
MapperScannerRegistrar向Spring容器中導(dǎo)入了MapperScannerConfigurer
3.2 MapperScannerConfigurer
MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor接口,將在BeanDefinitionRegistry被Spring創(chuàng)建好后被回調(diào)
回調(diào)postProcessBeanDefinitionRegistry方法
此時(shí)的流程如下圖所示

3.3 postProcessBeanDefinitionRegistry
接下來跟蹤MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { // 忽略 if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } // 1. 創(chuàng)建scanner, { ClassPathMapperScanner } 繼承了 { ClassPathBeanDefinitionScanner } // { this.$propertyName } 來自@MapperScan注解的屬性 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.valueOf(lazyInitialization)); } if (StringUtils.hasText(defaultScope)) { scanner.setDefaultScope(defaultScope); } scanner.registerFilters(); // 調(diào)用scan方法, 掃描包下的類 scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
3.4 scan
上面有提到,ClassPathMapperScanner繼承自ClassPathBeanDefinitionScanner
ClassPathMapperScanner并沒有重寫scan方法,這里還是調(diào)用的ClassPathBeanDefinitionScanner的scan方法
Spring的Bean掃描機(jī)制:ClassPathBeanDefinitionScanner源碼
在上篇文章中已經(jīng)追蹤過scan方法的具體流程了,就不再贅述
但是
ClassPathMapperScanner重寫了ClassPathBeanDefinitionScanner的doScan方法
接下來繼續(xù)跟追doScan方法
// ClassPathBeanDefinitionScanner 的 scan 方法源碼 public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }
3.5 doScan
注意這里是ClassPathMapperScanner重寫的doScan方法
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 1. 得到super ( ClassPathBeanDefinitionScanner ) 掃描后的 beanDefinition 集合 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { // 2. 對(duì) beanDefinition 集合做額外的處理 processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
為什么ClassPathMapperScanner要對(duì)掃描出來的BeanDefinition做額外的處理?
日常開發(fā)中,基本都是定義一個(gè)接口,作為Mapper
例如
public interface UserMapper { User selectById(int id); }
UserMapper被掃描出來后,ClassPathBeanDefinitionScanner為其創(chuàng)建對(duì)應(yīng)的BeanDefinition對(duì)象
BeanDefinition對(duì)象的BeanClass就是UserMapper.class
而一個(gè)接口是無法直接被Spring實(shí)例化的,我們需要的是SqlSession.getMapper創(chuàng)建出來的代理對(duì)象
ClassPathMapperScanner的額外處理就是修改BeanDefinition的屬性,使其最終還是通過SqlSession.getMapper方法創(chuàng)建Mapper對(duì)象
====================================================================================================
那么接下來繼續(xù)追蹤processBeanDefinitions方法,看看這個(gè)方法究竟做了什么額外處理
3.6 processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 // 注意這里: 修改了BeanClass definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName); // ... 忽略邊緣邏輯代碼 } }
在該方法中, 最最核心的一行代碼就是
definition.setBeanClass(this.mapperFactoryBeanClass);
mapperFactoryBeanClass屬性在ClassPathMapperScanner被定義
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { // .... private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class; // .... }
也就是說,ClassPathMapperScanner 將掃描出的BeanDefinition的BeanClass替換為了MapperFactoryBean

3.7 MapperFactoryBean
MapperFactoryBean是一個(gè)FactoryBean
我們關(guān)注該類的getObject方法
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
可以看到,兜兜轉(zhuǎn)轉(zhuǎn),最終還是通過SqlSession.getMapper方法來創(chuàng)建Mapper的
那么該對(duì)象的SqlSession和mapperInterface是怎么來的呢?
還是在processBeanDefinitions方法中被賦值的
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); // 1. 通過 { MapperFactoryBean } 的構(gòu)造方法注入 { mapperInterface } 屬性 // beanClassName也就是被掃描的接口全限定名, 例如 "com.zzzj.UserMapper" // 注意: MapperFactoryBean 的構(gòu)造方法接收一個(gè)Class對(duì)象, 而不是String對(duì)象 ( beanClassName是String類型的 ) // 在Spring調(diào)用 { MapperFactoryBean } 構(gòu)造方法前會(huì)進(jìn)行值的轉(zhuǎn)換 // 將String轉(zhuǎn)換為Class definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); definition.setBeanClass(this.mapperFactoryBeanClass); // 2. 這里賦值的 { sqlSessionTemplate } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } // ...... } }
4. 總結(jié)
- @MapperScan通過@Import注解導(dǎo)入了MapperScannerRegistrar
- MapperScannerRegistrar向容器中注入MapperScannerConfigurer
- MapperScannerConfigurer是一個(gè)BeanDefinitionRegistryPostProcessor,將會(huì)被Spring容器回調(diào)postProcessBeanDefinitionRegistry方法
- 在postProcessBeanDefinitionRegistry方法中創(chuàng)建了ClassPathMapperScanner,并且調(diào)用ClassPathMapperScanner的scan方法
- ClassPathMapperScanner重寫了父類ClassPathBeanDefinitionScanner的doScan方法,在掃描完Bean獲取到BeanDefinition后,在processBeanDefinitions方法對(duì)其進(jìn)行了額外的處理
- processBeanDefinitions方法將所有的BeanDefinition的beanClass屬性轉(zhuǎn)換為了MapperFactoryBean
- MapperFactoryBean是一個(gè)FactoryBean,將會(huì)被Spring回調(diào)getObject()方法,并且將方法的返回值注入到容器中
- MapperFactoryBean的getObject()方法通過sqlSession#getMapper方法創(chuàng)建Mapper對(duì)象,注入到容器中

浙公網(wǎng)安備 33010602011771號(hào)