欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入探究Java?@MapperScan實現(xiàn)原理

 更新時間:2023年01月04日 09:51:29   作者:程序員小潘  
之前是直接在Mapper類上面添加注解@Mapper,這種方式要求每一個mapper類都需要添加此注解,麻煩。通過使用@MapperScan可以指定要掃描的Mapper類的包的路徑,這篇文章深入探究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

    Java Spring詳解如何配置數(shù)據(jù)源注解開發(fā)以及整合Junit

    Spring 是目前主流的 Java Web 開發(fā)框架,是 Java 世界最為成功的框架。該框架是一個輕量級的開源框架,具有很高的凝聚力和吸引力,本篇文章帶你了解如何配置數(shù)據(jù)源、注解開發(fā)以及整合Junit
    2021-10-10
  • IDEA2023.1.3安裝教程及下載(圖文)

    IDEA2023.1.3安裝教程及下載(圖文)

    最新變化是在IDEA?2023.1中,對新UI做出了大量改進,本文主要介紹了IDEA2023.1.3安裝教程及下載,具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • ConstraintValidator類如何實現(xiàn)自定義注解校驗前端傳參

    ConstraintValidator類如何實現(xiàn)自定義注解校驗前端傳參

    這篇文章主要介紹了ConstraintValidator類實現(xiàn)自定義注解校驗前端傳參的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • FeignMultipartSupportConfig上傳圖片配置方式

    FeignMultipartSupportConfig上傳圖片配置方式

    這篇文章主要介紹了FeignMultipartSupportConfig上傳圖片配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • JAVA反射機制實例教程

    JAVA反射機制實例教程

    這篇文章主要介紹了JAVA反射機制,包括了Java反射機制的各種應(yīng)用技巧,非常具有實用價值,需要的朋友可以參考下
    2014-09-09
  • Java編程實現(xiàn)的模擬行星運動示例

    Java編程實現(xiàn)的模擬行星運動示例

    這篇文章主要介紹了Java編程實現(xiàn)的模擬行星運動,涉及java基于swing組建繪制動態(tài)效果及數(shù)值運算相關(guān)操作技巧,并總結(jié)分析了java面向?qū)ο蟮南嚓P(guān)特性,需要的朋友可以參考下
    2018-04-04
  • Java NIO原理圖文分析及代碼實現(xiàn)

    Java NIO原理圖文分析及代碼實現(xiàn)

    本文主要介紹Java NIO原理的知識,這里整理了詳細資料及簡單示例代碼和原理圖,有需要的小伙伴可以參考下
    2016-09-09
  • Java中WeakHashMap的回收問題詳解

    Java中WeakHashMap的回收問題詳解

    這篇文章主要介紹了Java中WeakHashMap的回收問題詳解,WeakHashMap弱鍵大致上是通過WeakReference和ReferenceQueue實現(xiàn),WeakHashMap的key是"弱鍵",即是WeakReference類型的,ReferenceQueue是一個隊列,它會保存被GC回收的"弱鍵",需要的朋友可以參考下
    2023-09-09
  • Mybatis Properties 配置優(yōu)先級詳解

    Mybatis Properties 配置優(yōu)先級詳解

    這篇文章主要介紹了Mybatis Properties 配置優(yōu)先級,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • JAVA入門教學(xué)之快速搭建基本的springboot(從spring boot到spring cloud)

    JAVA入門教學(xué)之快速搭建基本的springboot(從spring boot到spring cloud)

    本文主要入門者介紹怎么搭建一個基礎(chǔ)的springboot環(huán)境,本文通過圖文并茂的形式給大家介紹從spring boot到spring cloud的完美搭建過程,適用java入門教學(xué),需要的朋友可以參考下
    2021-02-02

最新評論