Mybatis源碼(一)利用springboot集成以及Mapper接口加載過程

從本篇文章開始,簡單分析下mybatis源碼。
本篇文章先分析下mapper接口是如何被掃描到spring容器中的。

springboot集成mybatis非常簡單,只需要在pom文件中添加如下依賴(本文分析的是1.3.2版本):

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
     <artifactId>mybatis-spring-boot-starter</artifactId>
     <version>1.3.2</version>
 </dependency>

1. mybatis如何被加載啓動

在使用mybatis時,我們寫了很多的mapper接口,接口上面使用了@Mapper註解修飾;但是此註解是mybais中的內容,並非spring,也就是說spring並不認識此註解,那麼這些mapper接口是如何被加載到spring容器中的呢?

這部分內容,實際上是springboot的內容,至於springboot的啓動流程以及原理,如果不清楚可以先看下之前的分析文章《springboot源碼(一)啓動流程+自動配置原理分析》

從這篇文章中,我們知道springboot默認會加載starter包中的spring.factories文件,並讀取其中的配置類。

上述mybatis-spring-boot-starter中並沒有這個spring.factories文件,但是當我們打開這個pom文件,發現它有如下依賴:

<dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>

從依賴的libraries中扎到此依賴,打開,發現存在spring.factories文件:
在這裏插入圖片描述
繼續打開此文件:
在這裏插入圖片描述
發現只有一個配置類MybatisAutoConfiguration,沒錯,入口就在這裏了。

2. MybatisAutoConfiguration

這是一個被@Configuration註解修飾bean,是配置類。
看一下類結構:
在這裏插入圖片描述
內容不多,包含兩個內部類,四個方法以及一些屬性。
先簡單介紹他們的作用:

  • MybatisAutoConfiguration():構造方法;
  • checkConfigFileExists():檢查是否需要加載存在的mybatis的config xml配置文件,有就加載;
  • sqlSessionFactory(): 初始化SqlSessionFactory對象,這個是Mybatis的核心類之一。
  • sqlSessionTemplate(): 初始化SqlSessionTemplate對象;
  • MapperScannerRegistrarNotFoundConfiguration:這是一個內部類,也是配置類,核心是上面的@import註解,內容是下面的內部類AutoConfiguredMapperScannerRegistrar;
  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {
    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }
  • AutoConfiguredMapperScannerRegistrar:也是個內部類,提供了掃描Mapper接口的方法;

我們看下這個AutoConfiguredMapperScannerRegistrar,其中有一個方法:

	@Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		//掃描被@Mapper修飾的mapper接口
      logger.debug("Searching for mappers annotated with @Mapper");

      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }
		//得到要掃描的包
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        if (logger.isDebugEnabled()) {
          for (String pkg : packages) {
            logger.debug("Using auto-configuration base package '{}'", pkg);
          }
        }
		//設置只掃描@Mapper註解修飾的bean
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        //執行掃描動作
        scanner.doScan(StringUtils.toStringArray(packages));
      } catch (IllegalStateException ex) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
      }
    }

此方法名字應該很熟悉了,他是ImportBeanDefinitionRegistrar接口方法的實現(不熟悉這個類的可以看下使用場景《spring @Import註解的作用和幾種使用方式》);

registerBeanDefinitions這個方法,看名字就知道它的作用是註冊bean定義,就是往spring容器中註冊,註冊的bean,就是mapper接口。作者通過第一行log打印出了此方法的功能:Searching for mappers annotated with @Mapper。

到這裏,我們基本已經知道了mybatis在springboot中到底是如何被掃描並註冊的。

下面我們詳細看下。

3. AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions

上面已經貼出了此方法的代碼,發現主要是通過ClassPathMapperScanner類來完成具體掃描動作。
其中核心方法是 scanner.doScan(StringUtils.toStringArray(packages));

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  //通過父類方法掃描
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
    //處理bean定義
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

首先調用父類方法掃描所有mapper,並得到bean定義的集合;
然後,通過processBeanDefinitions(beanDefinitions);又對bean的定義進行了二次加工:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    //遍歷每個mapper接口的bean定義進行加工處理
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      //構造函數中摻入mapper的名字
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      //每個mapper接口,都變爲了MapperFactoryBean;但是會通過上一行的構造函數參數(mapper名字)來區分每個mapper
      definition.setBeanClass(this.mapperFactoryBean.getClass());
	  //添加了一個屬性addToConfig,值爲true
      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      //下面兩個if進不去
      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進不去
      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);
      }
    }
  }

此方法修改了mapper的bean定義,比較重要的有:

  • 重置了beanClass爲代理類 MapperFactoryBean;此時mapper接口已經物是人非成爲別人了。
  • 因爲每個mapper都變成了MapperFactoryBean,爲了區分他們,MapperFactoryBean的構造函數參數,傳入了mapper接口的名字;
  • sqlSessionFactory屬性和sqlSessionTemplate屬性注意下,雖然這裏還沒把具體屬性設置進去,但是實例化的mapper的時候會用到這倆;
  • *definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);設置注入類型爲按類型注入;sqlSessionFactory屬性和sqlSessionTemplate屬性就是因此才set注入的。

上述過程只是bean定義過程,這倆屬性會在mapper bean實例化的之後,進行填充;

上面這幾點比較重要,後續文章動態代理mapper接口都跟他們有關係。

本文先簡單分析到這裏,主要分析了mapper的被加載流程,到這裏mapper已經被註冊了,
至於mapper接口的實例化以及如何被動態代理以及sql如何執行等詳細內容,放在後續文章中分析:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章