深入探究Java?@MapperScan實現(xiàn)原理
1. 前言
MyBatis在整合Spring的時候,只需要加如下 注解,就可以將Mapper實例注冊到IOC容器交給Spring管理,它是怎么做到的呢???
@MapperScan("com.xxx.mapper")提出幾個問題:
- Mapper接口不能實例化,對象是怎么來的?
- Mapper接口沒有加任何Spring相關(guān)注解,Spring憑什么管理這些Bean?
2. ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar是Spring提供的接口,屬于Spring的擴展點之一。該接口會暴露 BeanDefinitionRegistry對象,Spring允許我們手動往容器注冊自定義的BeanDefinition。
public interface ImportBeanDefinitionRegistrar {
/**
* 注冊自定義BeanDefinition
*
* @param importingClassMetadata 導(dǎo)入類的元數(shù)據(jù),被誰導(dǎo)入的
* @param registry BeanDefinition注冊器
*/
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
使用起來也很簡單,新建類實現(xiàn)ImportBeanDefinitionRegistrar接口,重寫registerBeanDefinitions()方法實現(xiàn)注冊自定義BeanDefinition的相關(guān)邏輯,然后通過@Import注解引入即可。
ImportBeanDefinitionRegistrar實例本身并不會注冊到容器,Spring僅僅是通過反射實例化對象,然后觸發(fā)registerBeanDefinitions()方法而已。
3. ConfigurationClassPostProcessor
ImportBeanDefinitionRegistrar擴展點是在哪里被觸發(fā)的呢???
AnnotationConfigApplicationContext類的構(gòu)造函數(shù)里會創(chuàng)建AnnotatedBeanDefinitionReader對象用來讀取并注冊基于注解的BeanDefinition,AnnotatedBeanDefinitionReader的構(gòu)造函數(shù)有一個特別重要的功能,就是往容器手動注冊Spring內(nèi)置的幾個非常重要的,用來支撐Spring底層核心功能的BeanDefinition,分別是:
- ConfigurationClassPostProcessor
- AutowiredAnnotationBeanPostProcessor
- CommonAnnotationBeanPostProcessor
- PersistenceAnnotationBeanPostProcessor
- EventListenerMethodProcessor
- DefaultEventListenerFactory
ConfigurationClassPostProcessor這個類特別特別重要,它做的事情包括:
- 解析
@ComponentScan注解掃描自定義的Bean。 - 解析
@PropertySources和@Value注解讀取配置文件屬性。 - 解析
@Import注解引入自定義類。 - 解析
@ImportResource注解引入外部Spring配置文件。 - 處理
@Bean注解方法。

ConfigurationClassPostProcessor實現(xiàn)了BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor接口。BeanFactoryPostProcessor也是Spring的擴展點之一,它允許開發(fā)者對BeanFactory進行擴展;BeanDefinitionRegistryPostProcessor擴展的語義更明確一些,它表示要對BeanFactory完成BeanDefinition的注冊。BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()會比BeanFactoryPostProcessor#postProcessBeanFactory()先執(zhí)行。
Spring啟動時,準(zhǔn)備好BeanFactory后就會開始觸發(fā)BeanFactoryPostProcessor擴展點,ConfigurationClassPostProcessor因為在構(gòu)造函數(shù)里已經(jīng)被注冊到容器中,所以會被執(zhí)行到。它會去解析ConfigurationClass是否有加@Import注解,如果加了該注解,且引入的類是ImportBeanDefinitionRegistrar子類,就會去實例化子類對象,然后執(zhí)行它的registerBeanDefinitions()方法。
4. MapperScannerRegistrar
查看@MapperScan注解發(fā)現(xiàn),它的確加了@Import注解,且引入的MapperScannerRegistrar類就是ImportBeanDefinitionRegistrar的子類。
@Retention(RetentionPolicy . RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {

也就是說Spring在啟動時,觸發(fā)ImportBeanDefinitionRegistrar擴展點的時候,會執(zhí)行MyBatis寫的MapperScannerRegistrar的擴展邏輯。其實從名字就可以看的出來,這個類的作用是完成MapperScanner的注冊工作,MapperScanner是啥?就是Mapper接口的掃描器了。
MapperScannerRegistrar的擴展邏輯很簡單,創(chuàng)建自定義的Bean掃描器ClassPathMapperScanner,然后掃描@MapperScan注解指定的包路徑。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 注解屬性
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
// 創(chuàng)建自定義的Mapper掃描器,用來掃描Mapper接口
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
// 如果指定了注解,則只掃描加了指定注解的Mapper接口
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
// 指定BeanName生成器,如果有
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 注冊過濾器,定義Bean的掃描規(guī)則
scanner.registerFilters();
// 開始掃描
scanner.doScan(StringUtils.toStringArray(basePackages));
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}具體的掃描工作交給了ClassPathMapperScanner類,它繼承自Spring提供的ClassPathBeanDefinitionScanner,就不用自己去實現(xiàn)掃描Class的邏輯了,這里用到了模板方法模式,子類通過重寫部分方法,來自定義Bean的掃描和注冊規(guī)則。
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 父類完成掃描,得到一組BeanDefinition
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
// 沒有符合的Bean,不做處理
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// 處理BeanDefinition,因為Mapper接口不能被實例化
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
調(diào)用父類的doScan()方法完成掃描得到一組BeanDefinition,如果有符合規(guī)則的BeanDefinition,這里需要做處理,不能直接返回。因為此時BeanDefinition的beanClass指向的是Mapper接口,直接注冊到容器的話,Spring不知道怎么實例化Bean。 所以,MyBatis還需要做點小動作,對BeanDefinition做一些修改。主要是重設(shè)beanClass,將其指向MapperFactoryBean。因為MapperFactoryBean是類,可以被實例化。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// MapperFactoryBean構(gòu)造函數(shù)需要MapperClass
// 這里是告訴Spring實例化MapperFactoryBean時構(gòu)造函數(shù)傳哪個Class
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
// 重設(shè)beanClass 指向MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
5. MapperFactoryBean
Mapper接口不能被實例化,所以MyBatis會將掃描到的屬于MapperClass的BeanDefinition做些修改,將beanClass指向MapperFactoryBean,這樣Spring在實例化Bean的時候就會去創(chuàng)建MapperFactoryBean實例了。
MapperFactoryBean實現(xiàn)了FactoryBean接口,SpringgetBean()時會判斷,如果一個BeanClass實現(xiàn)了FactoryBean接口,則不直接返回bean,而是返回FactoryBean#getObject()方法返回的對象。也就是說,本該由Spring完成的Bean實例化過程,交給了MyBatis自己來實現(xiàn)。
@Override
public T getObject() throws Exception {
// 基于Mapper接口生成代理對象
return getSqlSession().getMapper(this.mapperInterface);
}
通過MapperFactoryBean#getObject()發(fā)現(xiàn),MyBatis會調(diào)用SqlSession#getMapper()方法基于Mapper接口創(chuàng)建JDK動態(tài)代理對象。也就是說,Mapper接口對應(yīng)的BeanDefinition,對應(yīng)的在Spring容器里的對象是MyBatis生成的代理對象。
到此這篇關(guān)于深入探究Java @MapperScan實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Java @MapperScan內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Spring詳解如何配置數(shù)據(jù)源注解開發(fā)以及整合Junit
Spring 是目前主流的 Java Web 開發(fā)框架,是 Java 世界最為成功的框架。該框架是一個輕量級的開源框架,具有很高的凝聚力和吸引力,本篇文章帶你了解如何配置數(shù)據(jù)源、注解開發(fā)以及整合Junit2021-10-10
ConstraintValidator類如何實現(xiàn)自定義注解校驗前端傳參
這篇文章主要介紹了ConstraintValidator類實現(xiàn)自定義注解校驗前端傳參的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
FeignMultipartSupportConfig上傳圖片配置方式
這篇文章主要介紹了FeignMultipartSupportConfig上傳圖片配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Mybatis Properties 配置優(yōu)先級詳解
這篇文章主要介紹了Mybatis Properties 配置優(yōu)先級,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
JAVA入門教學(xué)之快速搭建基本的springboot(從spring boot到spring cloud)
本文主要入門者介紹怎么搭建一個基礎(chǔ)的springboot環(huán)境,本文通過圖文并茂的形式給大家介紹從spring boot到spring cloud的完美搭建過程,適用java入門教學(xué),需要的朋友可以參考下2021-02-02

