深入探究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啟動時,準備好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-10ConstraintValidator類如何實現(xiàn)自定義注解校驗前端傳參
這篇文章主要介紹了ConstraintValidator類實現(xiàn)自定義注解校驗前端傳參的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06FeignMultipartSupportConfig上傳圖片配置方式
這篇文章主要介紹了FeignMultipartSupportConfig上傳圖片配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Mybatis Properties 配置優(yōu)先級詳解
這篇文章主要介紹了Mybatis Properties 配置優(yōu)先級,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07JAVA入門教學(xué)之快速搭建基本的springboot(從spring boot到spring cloud)
本文主要入門者介紹怎么搭建一個基礎(chǔ)的springboot環(huán)境,本文通過圖文并茂的形式給大家介紹從spring boot到spring cloud的完美搭建過程,適用java入門教學(xué),需要的朋友可以參考下2021-02-02