深入探究Java?@MapperScan實(shí)現(xiàn)原理
1. 前言
MyBatis在整合Spring的時(shí)候,只需要加如下 注解,就可以將Mapper實(shí)例注冊(cè)到IOC容器交給Spring管理,它是怎么做到的呢???
@MapperScan("com.xxx.mapper")
提出幾個(gè)問(wèn)題:
- Mapper接口不能實(shí)例化,對(duì)象是怎么來(lái)的?
- Mapper接口沒有加任何Spring相關(guān)注解,Spring憑什么管理這些Bean?
2. ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar是Spring提供的接口,屬于Spring的擴(kuò)展點(diǎn)之一。該接口會(huì)暴露 BeanDefinitionRegistry對(duì)象,Spring允許我們手動(dòng)往容器注冊(cè)自定義的BeanDefinition。
public interface ImportBeanDefinitionRegistrar { /** * 注冊(cè)自定義BeanDefinition * * @param importingClassMetadata 導(dǎo)入類的元數(shù)據(jù),被誰(shuí)導(dǎo)入的 * @param registry BeanDefinition注冊(cè)器 */ void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); }
使用起來(lái)也很簡(jiǎn)單,新建類實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口,重寫registerBeanDefinitions()方法實(shí)現(xiàn)注冊(cè)自定義BeanDefinition的相關(guān)邏輯,然后通過(guò)@Import
注解引入即可。
ImportBeanDefinitionRegistrar實(shí)例本身并不會(huì)注冊(cè)到容器,Spring僅僅是通過(guò)反射實(shí)例化對(duì)象,然后觸發(fā)registerBeanDefinitions()
方法而已。
3. ConfigurationClassPostProcessor
ImportBeanDefinitionRegistrar擴(kuò)展點(diǎn)是在哪里被觸發(fā)的呢???
AnnotationConfigApplicationContext類的構(gòu)造函數(shù)里會(huì)創(chuàng)建AnnotatedBeanDefinitionReader對(duì)象用來(lái)讀取并注冊(cè)基于注解的BeanDefinition,AnnotatedBeanDefinitionReader的構(gòu)造函數(shù)有一個(gè)特別重要的功能,就是往容器手動(dòng)注冊(cè)Spring內(nèi)置的幾個(gè)非常重要的,用來(lái)支撐Spring底層核心功能的BeanDefinition,分別是:
- ConfigurationClassPostProcessor
- AutowiredAnnotationBeanPostProcessor
- CommonAnnotationBeanPostProcessor
- PersistenceAnnotationBeanPostProcessor
- EventListenerMethodProcessor
- DefaultEventListenerFactory
ConfigurationClassPostProcessor這個(gè)類特別特別重要,它做的事情包括:
- 解析
@ComponentScan
注解掃描自定義的Bean。 - 解析
@PropertySources
和@Value
注解讀取配置文件屬性。 - 解析
@Import
注解引入自定義類。 - 解析
@ImportResource
注解引入外部Spring配置文件。 - 處理
@Bean
注解方法。
ConfigurationClassPostProcessor實(shí)現(xiàn)了BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor接口。BeanFactoryPostProcessor也是Spring的擴(kuò)展點(diǎn)之一,它允許開發(fā)者對(duì)BeanFactory進(jìn)行擴(kuò)展;BeanDefinitionRegistryPostProcessor擴(kuò)展的語(yǔ)義更明確一些,它表示要對(duì)BeanFactory完成BeanDefinition的注冊(cè)。BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()
會(huì)比BeanFactoryPostProcessor#postProcessBeanFactory()
先執(zhí)行。
Spring啟動(dòng)時(shí),準(zhǔn)備好BeanFactory后就會(huì)開始觸發(fā)BeanFactoryPostProcessor擴(kuò)展點(diǎn),ConfigurationClassPostProcessor因?yàn)樵跇?gòu)造函數(shù)里已經(jīng)被注冊(cè)到容器中,所以會(huì)被執(zhí)行到。它會(huì)去解析ConfigurationClass是否有加@Import
注解,如果加了該注解,且引入的類是ImportBeanDefinitionRegistrar子類,就會(huì)去實(shí)例化子類對(duì)象,然后執(zhí)行它的registerBeanDefinitions()
方法。
4. MapperScannerRegistrar
查看@MapperScan
注解發(fā)現(xiàn),它的確加了@Import
注解,且引入的MapperScannerRegistrar類就是ImportBeanDefinitionRegistrar的子類。
@Retention(RetentionPolicy . RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
也就是說(shuō)Spring在啟動(dòng)時(shí),觸發(fā)ImportBeanDefinitionRegistrar擴(kuò)展點(diǎn)的時(shí)候,會(huì)執(zhí)行MyBatis寫的MapperScannerRegistrar的擴(kuò)展邏輯。其實(shí)從名字就可以看的出來(lái),這個(gè)類的作用是完成MapperScanner的注冊(cè)工作,MapperScanner是啥?就是Mapper接口的掃描器了。
MapperScannerRegistrar的擴(kuò)展邏輯很簡(jiǎn)單,創(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掃描器,用來(lái)掃描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)); } // 注冊(cè)過(guò)濾器,定義Bean的掃描規(guī)則 scanner.registerFilters(); // 開始掃描 scanner.doScan(StringUtils.toStringArray(basePackages)); } /** * {@inheritDoc} */ @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } }
具體的掃描工作交給了ClassPathMapperScanner類,它繼承自Spring提供的ClassPathBeanDefinitionScanner,就不用自己去實(shí)現(xiàn)掃描Class的邏輯了,這里用到了模板方法模式,子類通過(guò)重寫部分方法,來(lái)自定義Bean的掃描和注冊(cè)規(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,因?yàn)镸apper接口不能被實(shí)例化 processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
調(diào)用父類的doScan()
方法完成掃描得到一組BeanDefinition,如果有符合規(guī)則的BeanDefinition,這里需要做處理,不能直接返回。因?yàn)榇藭r(shí)BeanDefinition的beanClass指向的是Mapper接口,直接注冊(cè)到容器的話,Spring不知道怎么實(shí)例化Bean。 所以,MyBatis還需要做點(diǎn)小動(dòng)作,對(duì)BeanDefinition做一些修改。主要是重設(shè)beanClass,將其指向MapperFactoryBean。因?yàn)镸apperFactoryBean是類,可以被實(shí)例化。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); // MapperFactoryBean構(gòu)造函數(shù)需要MapperClass // 這里是告訴Spring實(shí)例化MapperFactoryBean時(shí)構(gòu)造函數(shù)傳哪個(gè)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接口不能被實(shí)例化,所以MyBatis會(huì)將掃描到的屬于MapperClass的BeanDefinition做些修改,將beanClass指向MapperFactoryBean,這樣Spring在實(shí)例化Bean的時(shí)候就會(huì)去創(chuàng)建MapperFactoryBean實(shí)例了。
MapperFactoryBean實(shí)現(xiàn)了FactoryBean接口,SpringgetBean()
時(shí)會(huì)判斷,如果一個(gè)BeanClass實(shí)現(xiàn)了FactoryBean接口,則不直接返回bean,而是返回FactoryBean#getObject()
方法返回的對(duì)象。也就是說(shuō),本該由Spring完成的Bean實(shí)例化過(guò)程,交給了MyBatis自己來(lái)實(shí)現(xiàn)。
@Override public T getObject() throws Exception { // 基于Mapper接口生成代理對(duì)象 return getSqlSession().getMapper(this.mapperInterface); }
通過(guò)MapperFactoryBean#getObject()
發(fā)現(xiàn),MyBatis會(huì)調(diào)用SqlSession#getMapper()
方法基于Mapper接口創(chuàng)建JDK動(dòng)態(tài)代理對(duì)象。也就是說(shuō),Mapper接口對(duì)應(yīng)的BeanDefinition,對(duì)應(yīng)的在Spring容器里的對(duì)象是MyBatis生成的代理對(duì)象。
到此這篇關(guān)于深入探究Java @MapperScan實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Java @MapperScan內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Spring詳解如何配置數(shù)據(jù)源注解開發(fā)以及整合Junit
Spring 是目前主流的 Java Web 開發(fā)框架,是 Java 世界最為成功的框架。該框架是一個(gè)輕量級(jí)的開源框架,具有很高的凝聚力和吸引力,本篇文章帶你了解如何配置數(shù)據(jù)源、注解開發(fā)以及整合Junit2021-10-10ConstraintValidator類如何實(shí)現(xiàn)自定義注解校驗(yàn)前端傳參
這篇文章主要介紹了ConstraintValidator類實(shí)現(xiàn)自定義注解校驗(yàn)前端傳參的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06FeignMultipartSupportConfig上傳圖片配置方式
這篇文章主要介紹了FeignMultipartSupportConfig上傳圖片配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java編程實(shí)現(xiàn)的模擬行星運(yùn)動(dòng)示例
這篇文章主要介紹了Java編程實(shí)現(xiàn)的模擬行星運(yùn)動(dòng),涉及java基于swing組建繪制動(dòng)態(tài)效果及數(shù)值運(yùn)算相關(guān)操作技巧,并總結(jié)分析了java面向?qū)ο蟮南嚓P(guān)特性,需要的朋友可以參考下2018-04-04Java NIO原理圖文分析及代碼實(shí)現(xiàn)
本文主要介紹Java NIO原理的知識(shí),這里整理了詳細(xì)資料及簡(jiǎn)單示例代碼和原理圖,有需要的小伙伴可以參考下2016-09-09Mybatis Properties 配置優(yōu)先級(jí)詳解
這篇文章主要介紹了Mybatis Properties 配置優(yōu)先級(jí),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07JAVA入門教學(xué)之快速搭建基本的springboot(從spring boot到spring cloud)
本文主要入門者介紹怎么搭建一個(gè)基礎(chǔ)的springboot環(huán)境,本文通過(guò)圖文并茂的形式給大家介紹從spring boot到spring cloud的完美搭建過(guò)程,適用java入門教學(xué),需要的朋友可以參考下2021-02-02