mybaits是通過@MapperScan注解完成掃描的寻定,具體是如何完成的呢?首先看一下MapperScan的源碼:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
...注解體省略...
}
@MapperScan上加了一個@Import注解,了解注解知識的讀者都知道侦另,@MapperScan起始就是@Import的子類。在上一篇博客中我們詳細分析了注冊掃描后置處理器ConfigurationClassPostProcessor
是如何完成掃描的截酷,其中有一個重要的步驟就是解析@Import注解势篡。mybatis就是利用了這個特性完成的掃描。
回顧以下上一篇博文ConfigurationClassPostProcessor
解析@Import注解的過程:
定位到ConfigurationClassParser
類的doProcessConfigurationClass
方法中的如下代碼:
processImports(configClass, sourceClass, getImports(sourceClass), true);
這個方法就是處理配置類的@Import注解耳幢。起始上一篇博文已經分析過了岸晦,這里我們再分析一遍。
首先睛藻,通過getImports(sourceClass)
方法獲取配置類上所有的@Import注解中的類启上。加入我們有一個配置類如下所示:
@Configuration
@ComponentScan(value = "com")
@Import(User.class)
@MapperScan("net")
public class Config extends ConfigSuperClass implements UserInterface {
@Value("${demo.name}")
private String name;
public String getName() {
return name;
}
}
getImports
方法就是拿到@Import(User.class)
上的User.class和@MapperScan("net")
父類上的MapperScannerRegistrar.class
,然后將這兩個class封裝成SourceClass并返回店印。分析一下getImports
源碼:
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
這個方法中的核心方法是collectImports(sourceClass, imports, visited);
冈在,將找到的class加入到imports集合中,然后返回按摘。
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
//如果注解本身不是@Import包券,遞歸查看注解的父注解是否有@Import注解
collectImports(annotation, imports, visited);
}
}
//拿到@Import注解上的value值
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
總之,就是
1.拿到配置類上所有的注解
2.遞歸拿到所有注解的父注解
3.在所有的注解中找到@Import注解炫贤,拿到value值溅固,放到imports集合中返回。
在本例中兰珍,此時我們拿到了兩個class发魄,一個是配置類本身的@Import注解中的User.class,另一個是@MapperScan的父注解@Import中的MapperScannerRegistrar.class。
拿到import導入的兩個類后,就是真正的進入
processImports
方法進行處理了励幼,核心代碼如下:
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 反射創(chuàng)建這個類的實例對象
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
//是否有實現(xiàn)相關Aware接口,如果有,這調用相關方法
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// 延遲加載的ImportSelector
if (selector instanceof DeferredImportSelector) {
// 延遲加載的ImportSelector先放到List中,延遲加載
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}else {
// 普通的ImportSelector ,執(zhí)行其selectImports方法,獲取需要導入的類的全限定類名數(shù)組
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
//獲取需要導入類的class
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 遞歸調用
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
- 首先汰寓,
if (candidate.isAssignable(ImportSelector.class))
判斷導入的類是否實現(xiàn)了ImportSelector
接口。該接口源碼如下:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
接口只定義了一個selectImports(AnnotationMetadata importingClassMetadata)
方法苹粟,用于指定需要注冊到容器中的Class名稱有滑。當配置類的@Import
注解引入了一個ImportSelector
接口實現(xiàn)類后,會根據(jù)selectImports
方法返回的Class名稱生成BeanDefinition加載到spring容器中嵌削。
來看一個簡單的示例:
public class User2 {
}
@Component
public class User implements ImportSelector ,BeanFactoryAware {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {User2.class.getName()};
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("實現(xiàn)了BeanFactoryAware接口");
}
}
上文中毛好,我們在配置類上@Import(User.class)導入了User,此時拿到User的selectImports方法返回的類名苛秕,根據(jù)類名生成對應的BeanDefinition加載到容器中肌访。在這里,把User2定義成了BeanDefinition艇劫,加載到容器中了吼驶。看源碼如何實現(xiàn)的:
//如果實現(xiàn)了ImportSelector接口
if (candidate.isAssignable(ImportSelector.class)) {
// 獲取User的class對象
Class<?> candidateClass = candidate.loadClass();
// 反射創(chuàng)建這個類的實例對象
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
//是否有實現(xiàn)相關Aware接口,如果有,這調用相關方法
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// 延遲加載的ImportSelector
if (selector instanceof DeferredImportSelector) {
// 延遲加載的ImportSelector先放到List中,延遲加載
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}else {
// 普通的ImportSelector ,執(zhí)行其selectImports方法,獲取需要導入的類的全限定類名數(shù)組
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 遞歸調用
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
代碼注釋講的很清楚店煞,相信大家能看懂蟹演,但是有這么行代碼```
ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);
判斷是否有實現(xiàn)相關Aware接口,如果有,這調用相關方法。Aware顷蟀,是感應和感知的意思酒请。當User實現(xiàn)了對應的Aware接口時,此處就會User實現(xiàn)該接口的實現(xiàn)方法鸣个,此例中就是User的setBeanFactory(BeanFactory beanFactory)
方法羞反。該方法傳入了一個BeanFactory類型的參數(shù),這個類型的有以下這些方法:
這樣我們就能利用這個Bean工廠做很多很多擴展囤萤。這就是spring插件或者擴展開發(fā)的一個簡單案例苟弛。
Aware是頂級接口,它有很多子接口:
但是在此處不是所有有Aware接口都可以阁将,invokeAwareMethods源碼:
public static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
if (parserStrategyBean instanceof Aware) {
if (parserStrategyBean instanceof BeanClassLoaderAware) {
ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
if (classLoader != null) {
((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
}
}
if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
}
if (parserStrategyBean instanceof EnvironmentAware) {
((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
}
if (parserStrategyBean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
}
}
}
只有BeanClassLoaderAware膏秫、BeanFactoryAware 、EnvironmentAware做盅、ResourceLoaderAware接口才能被調用缤削。
Aware接口處理完后,就是判斷User是否是延遲加載:
if (selector instanceof DeferredImportSelector)
顯然吹榴,我們的User實現(xiàn)的ImportSelector接口亭敢,沒有實現(xiàn)DeferredImportSelector接口,所以就不是延遲加載图筹,DeferredImportSelector是ImportSelector的子接口帅刀。如果我們要實現(xiàn)延遲加載功能让腹,讓User實現(xiàn)DeferredImportSelector接口即可:
public class User implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("延遲加載User2");
return new String[] {User2.class.getName()};
}
}
啟動spring,就會進入上面的if判斷里的this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector)
方法扣溺,handle方法內會將selector再封裝成DeferredImportSelectorHolder類型的對象骇窍,然后保存在數(shù)組中以后調用,調用時機在以后的實例化過程中再講锥余,先記住這點腹纳。
如果不是延遲加載的話,if條件不執(zhí)行驱犹,執(zhí)行else中的語句嘲恍。String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
就是調用下面的方法,獲取你的返回的類名數(shù)組雄驹。
else中的最后兩行代碼就是根據(jù)上一步返回的類名數(shù)組找到對應的class佃牛,然后遞歸。為什么要遞歸調用医舆,因為你導入的類可能還有Import注解啊俘侠,那也要處理的。
- 其次彬向,
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))
判斷是否實現(xiàn)ImportBeanDefinitionRegistrar
接口兼贡。該接口允許我們手動封裝BeanDefinition并注冊到容器中攻冷。我們先修改下User類:
@Component
public class User implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("實現(xiàn)了ImportBeanDefinitionRegistrar接口");
}
}
其實娃胆,很小兒科,不講了等曼,在BeanDefinition博文中講爛了里烦。注意此時并沒有立刻調用registerBeanDefinitions
這個方法,而是先通過
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
這行代碼放到配置類對應的解析器的一個集合中禁谦,
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
new LinkedHashMap<>();
......
public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}
當配置類都處理完了后胁黑,遍歷所有配置類解析器,調用這個集合中所有類的addImportBeanDefinitionRegistrar方法州泊。
- 最后丧蘸,如果以上接口都沒實現(xiàn)。按普通配置類處理遥皂,遞歸調用力喷,跟Config 配置類的處理方式一樣,再來一遍上述過程演训。
也就是說弟孟,如果Import進來的類實現(xiàn)了ImportBeanDefinitionRegistrar 或 ImportSelector 接口,則繼續(xù)遞歸處理他們生成的類样悟,直到找到沒有實現(xiàn)這兩個接口的類拂募,找到后就當作配置類按照Config的方式進行處理庭猩。
MapperScannerRegistrar
上面講了User,其實MapperScannerRegistrar 也一樣
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
spring會調用MapperScannerRegistrar
的registerBeanDefinitions
方法陈症。
上面講了一堆蔼水,就是為了說明spring是如何調用MapperScannerRegistrar
的registerBeanDefinitions
方法的。下一步就是分析registerBeanDefinitions
這個方法即可爬凑。
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//解析MapperScan具體信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
先解析MapperScan類的基礎配置信息徙缴,起始就是拿到MapperScan所有方法的默認返回值。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
//掃描路徑數(shù)組
String[] value() default {};
//掃描路徑數(shù)組嘁信,另一種表達方式于样,跟上面一樣
String[] basePackages() default {};
//類數(shù)組,根據(jù)類獲取所在的包路徑潘靖,作為基礎掃描類型
Class<?>[] basePackageClasses() default {};
//名字生成器穿剖,配置路徑下的類名根據(jù)此名字生成器進行命名
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
//待掃描的注解,注冊基本包中具有指定注解的所有接口卦溢。注意這可以與markerInterface結合使用
Class<? extends Annotation> annotationClass() default Annotation.class;
//掃描指定的類
Class<?> markerInterface() default Class.class;
//如果spring中有多個sqlSessionTemplate糊余,指定你要用哪一個,通常只有當您有多個數(shù)據(jù)源時才需要這樣做
String sqlSessionTemplateRef() default "";
//如果spring中有多個SqlSessionFactory单寂,指定你要用哪一個贬芥,通常只有當您有多個數(shù)據(jù)源時才需要這樣做
String sqlSessionFactoryRef() default "";
//Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean mapper的創(chuàng)建工廠
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
//對于mapper是否懶加載
String lazyInitialization() default "";
}
然后調用registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)
方法,
第一步
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
將MapperScannerConfigurer生成一個GenericBeanDefinition類型的BeanDefinition再返回來備用宣决,看源碼:
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
//構建一個GenericBeanDefinition
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
//GenericBeanDefinition與MapperScannerConfigurer對應
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
第二步設置屬性processPropertyPlaceHolders
為true
蘸劈,下文講為什么
builder.addPropertyValue("processPropertyPlaceHolders", true);
第三步找到掃描的包路徑,放到basePackages集合中尊沸,如果沒有配置掃描路徑威沫,則將@MapperScan所在的配置類的包路徑作為基礎包路徑:
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
然后就將將MapperScannerConfigurer對應的GenericBeanDefinition注冊到容器中
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
好像,沒有掃描啊洼专,只是做了一些配置工作棒掠,說實話,如果讀者沒有讀過我之前關于后置處理器的文章屁商,很難搞懂接下來我要講的內容——自定義后置處理器何時調用烟很? 建議先閱讀《逐行閱讀Spring5.X源碼(五) 初探BeanFactoryPostProcessor后置處理器,難蜡镶,特別難雾袱。》
MapperScannerConfigurer
我們首先要搞清楚帽哑,上文注冊到容器中的MapperScannerConfigurer
到底是個什么鬼谜酒?自動掃描 將Mapper接口生成代理注入到Spring。
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
顧名思義妻枕,mapper掃描配置類僻族,這個類實現(xiàn)了BeanDefinitionRegistryPostProcessor
接口粘驰! 變成了一個后置處理器。該接口只有一個postProcessBeanDefinitionRegistry
方法述么,spring啟動過程中會主動調用MapperScannerConfigurer
的postProcessBeanDefinitionRegistry
方法蝌数。所以接下來就要分析這個方法
這個方法第一步
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
processPropertyPlaceHolders
,上文講過這個值確實是true度秘,所以這條判斷成立顶伞,執(zhí)行processPropertyPlaceHolders();
方法。此函數(shù)的官方注解如下:
/*
* BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
* PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
* fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
* definition. Then update the values.
*/
大概意思就是:BeanDefinitionRegistries會在應用啟動的時候調用剑梳,并且會早于BeanFactoryPostProcessors的調用(在之前后置處理器博文中講過唆貌,確實是
),這就意味著PropertiesResourceConfigurers還沒有被加載垢乙,所有對于屬性文件的引用將會失效锨咙,為避免此種情況發(fā)生,此方法手動地找出定義的PropertyResourceConfigurers
并進行調用以以保證對于屬性的引用可以正常工作追逮。這個函數(shù)主要做以下兩件事情:
- 找到所有已經注冊的PropertyResourceConfigurer類型的bean酪刀。
- 模擬Spring的環(huán)境來用處理器。這里通過使用呢new DefaultlistableBeanFactory()來模擬Spring中的環(huán)境(完成處理器的調用后便失效)钮孵,將映射的bean骂倘,也就是MapperScannerConfigurer類型bean注冊到環(huán)境中來進行后處理器的調用。
可以先不用關注這點巴席,這個會在mybatis源碼專題中詳解历涝,下一步我們看mybatis如何掃描。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
ClassPathMapperScanner
類繼承了spring的ClassPathBeanDefinitionScanner
類情妖,專門用來掃描注冊
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
如果讀者閱讀過本專題之前的文章睬关,肯定對這個類非常了解诱担,如果沒讀過毡证,可以 參考《逐行閱讀Spring5.X源碼(六) ClassPathBeanDefinitionScanner掃描器》
和逐行閱讀Spring5.X源碼(番外篇)自定義掃描器, Mybatis是如何利用spring完成Mapper掃描的