Spring源码分析十:SpringBoot中Mybatis的自动化配置

一、前言

本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。


本文并非是讲解 Mybatis 源码!!!!而是讲解在SpringBoot 中如何实现MyBatis 的自动装配过程。

由于 MyBatis 直接引入对应依赖即可调用,而不需要通过 @EnableXXX 等注解来启用该功能,所以我们推测在 Springboot 中,MyBatis 是通过 Springboot 的自动装配实现的功能注入。


从pom 文件中我们引入了

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

我们可以推测自动化配置的内容肯定在于此。从 spring.factories 文件我们能知道,MybatisAutoConfiguration 是MyBatis 被引入的关键类。

在这里插入图片描述

所以我们下面来看看 MybatisAutoConfiguration 完成了什么工作

二、MybatisAutoConfiguration

我们首先来看 MybatisAutoConfiguration 类的结构,以下代码经过精简。

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean{
	  @Override
  public void afterPropertiesSet() {
    checkConfigFileExists();
  }

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  	...
  }

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  	...
  }
	
  public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
 	...
  }

  @org.springframework.context.annotation.Configuration
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
    }

  }
}

从上面的内容我们可以了解到 MybatisAutoConfiguration 得知以下几件事:

  1. MybatisAutoConfiguration 作为配置类,依赖于SqlSessionFactorySqlSessionFactoryBeanDataSourceDataSourceAutoConfigurationMybatisLanguageDriverAutoConfiguration等类。同时引入了MybatisProperties 作为 MyBatis 相关配置类。同时实现了InitializingBean 接口,在 创建的时候回调用 afterPropertiesSet 方法。
  2. 若其他地方未注入 SqlSessionFactory, 则 MybatisAutoConfiguration 进行注入。这里注入的是通过SqlSessionFactoryBean.getObject 获取的,后面会解释。
  3. 若其他地方未注入 SqlSessionTemplate, 则 MybatisAutoConfiguration 进行注入
  4. 若其他地方未注入 MapperFactoryBeanMapperScannerConfigurer, 则 MybatisAutoConfiguration 进行注入,并引入 AutoConfiguredMapperScannerRegistrar 类。

我们主要关注下面几个点。

1. MybatisAutoConfiguration 的声明。

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {

MybatisAutoConfiguration 的声明上面有一大堆注解。

  • @Configuration : 声明该类为配置类,不用多说
  • @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) : 表示在容器中注入了 SqlSessionFactory、SqlSessionFactoryBean 后才会注入该bean。这里 SqlSessionFactory 类是MyBatis 的的功能的基础, 而SqlSessionFactoryBean 则是实现了 FactoryBean 接口,也是为了适合Spring 获取 SqlSessionFactory。
  • @ConditionalOnSingleCandidate(DataSource.class) : 表示 容器中有单例的 DataSource 时才注入容器
  • @EnableConfigurationProperties(MybatisProperties.class) : 启用配置属性。MybatisProperties 中保存了Mybatis 的相关配置信息
  • @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) : 作用是控制配置类的加载顺序,在DataSourceAutoConfigurationMybatisLanguageDriverAutoConfiguration加载后加载本类。

2. SqlSessionFactory 的注入

关于 SqlSessionFactory 的配置,Mybatis 可以通过 xml的配置类方式进行,但是在Springboot中,我们也可以通过 yml 配置文件直接进行配置(这里说的yml只是SpringBoot配置文件的一种,并不代表只能用yml。下同,yml代表SpringBoot的配置文件配置),如下的两种配置就等价。
在这里插入图片描述

即: Mybatis 的配置有两种,一种通过 xml配置,这种需要制定xml的路径,即 ConfigLocation。
另一种像上面的yml配置,这种需要保存其配置的属性,即 ConfigurationProperties


对于 MyBatis 来说,其提供的SqlSessionFactory 注入方式是通过Xml 方式进行注册。而Springboot扩展了通过yml 配置文件进行配置。所以我们来看看 MybatisAutoConfiguration中对于 sqlSessionFactory 的注入。这里可以看到sqlSessionFactory 的注入是通过 SqlSessionFactoryBean.getObject 实现的。

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    // 如果配置了 配置文件地址 config-location 保存起来
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
 	// 应用自定义配置,如果  config-location 存在则不使用该配置
    applyConfiguration(factory);
    // 如果配置了配置属性,保存属性
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    // 下面同样逻辑,保存各种配置。
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (this.properties.getTypeAliasesSuperType() != null) {
      factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
      factory.setTypeHandlers(this.typeHandlers);
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    Set<String> factoryPropertyNames = Stream
        .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
      // Need to mybatis-spring 2.0.2+
      factory.setScriptingLanguageDrivers(this.languageDrivers);
      if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
        defaultLanguageDriver = this.languageDrivers[0].getClass();
      }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
      // Need to mybatis-spring 2.0.2+
      factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }
	// 获取 SqlSessionFactory 
    return factory.getObject();
  }

可以看到 这里是通过 SqlSessionFactoryBean 将各种属性保存后通过getObject 返回了封装好的 SqlSessionFactory。SqlSessionFactoryBean 很明显是一个 FactoryBean,FactoryBean 最大的好处是给予了我们自定义Bean的创建过程,而这里通过读取了 yml文件中的配置,通过 SqlSessionFactoryBean 来定制了 SqlSessionFactory 。


我们这里直接来看 SqlSessionFactoryBean.getObject 是如何初始化 SqlSessionFactory 的

可以看到,getObject 时会判断 sqlSessionFactory 是否初始化,没有则会对 sqlSessionFactory 进行初始化

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    // 如果使用了yml 配置。则使用 yml配置
    if (this.configuration != null) {
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
      // 如果指定了xml配置文件,则使用xml配置文件的配置。
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
	// 下面进行一大堆属性的赋值,不再赘述
    if (hasLength(this.typeAliasesPackage)) {
    	// 过滤出 非匿名类 && 非接口 && 非内部类 进行别名注册
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
          .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }

    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }

    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }

    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

上面可以看出 :

  1. SpringBoot 配置方式优先级高于 XML配置方式
  2. SpringBoot 通过SqlSessionFactoryBean 的方式,在原先 Mybatis 的基础上又封装了一次,以适应 SpringBoot 配置文件方式的配置。

3. AutoConfiguredMapperScannerRegistrar 的注入

上面一步适配了 Springboot的配置方式配置,将 SqlSessionFactory 注册到了 Spring容器中。而 MyBatis 本身提供了一个@Mapper 注解,被这个注解标注的 接口会被注入到Spring容器中,并与Mapper文件一一映射,但是Spring本身并不支持 @Mapper注解的扫描。那么@Mapper的工作就需要 MyBatis 自己完成, MyBatis 通过 MapperScannerConfigurer 来进行对 Mapper的扫描。而 AutoConfiguredMapperScannerRegistrar 中则是将 MapperScannerConfigurer 注册到Spring中。


AutoConfiguredMapperScannerRegistrar 是在 MapperScannerRegistrarNotFoundConfiguration 是声明中通过 @Import 注解引入的。

  @org.springframework.context.annotation.Configuration
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

我们看 AutoConfiguredMapperScannerRegistrar 的声明,实现了 BeanFactoryAware(可以获得 BeanFactory),实现了ImportBeanDefinitionRegistrar 接口,我们可以通过这个接口提供的registerBeanDefinitions 方法中完成BeanDefinition 的注册修改等操作,具体逻辑在 Spring 源码分析衍生篇七 :ConfigurationClassPostProcessor 上篇 中有过分析。

下面是详细代码

 public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		// 对自动扫描功能是否启用的校验 : 容器中是否注册了 AutoConfigurationPackages
      if (!AutoConfigurationPackages.has(this.beanFactory)) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
        return;
      }

      logger.debug("Searching for mappers annotated with @Mapper");
		// 获取 AutoConfigurationPackages 指定的扫描的路径
      List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
      if (logger.isDebugEnabled()) {
        packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
      }
	  // 创建 BeanDefinition 的创建器,并添加指定的属性
      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
      builder.addPropertyValue("processPropertyPlaceHolders", true);
      // 设置需要扫描的注解 @Mapper
      builder.addPropertyValue("annotationClass", Mapper.class);
      // 设置扫描路径
      builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
      BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
      // 对懒加载属性的设置
      Stream.of(beanWrapper.getPropertyDescriptors())
          // Need to mybatis-spring 2.0.2+
          .filter(x -> x.getName().equals("lazyInitialization")).findAny()
          .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
      // 将 MapperScannerConfigurer 的 BeanDefinition 注册到 Spring容器中
      registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
    }

  }

逻辑很简单,Spring不给我扫描@Mapper,我自己创建一个 MapperScannerConfigurer 扫描一遍@Mapper 注解注入不就行了。

4. MapperScannerConfigurer

在上面的 AutoConfiguredMapperScannerRegistrar 的声明中,我们看到MapperScannerRegistrarNotFoundConfiguration需要在 MapperFactoryBeanMapperScannerConfigurer 不存在时注入,在 AutoConfiguredMapperScannerRegistrar 中对 MapperScannerConfigurer 进行了注入,完成了 @Mapper 注解的扫描。下面我们就来看看 MapperScannerConfigurer 的实现是如何扫描@Mapper 注解的。

需要注意的是,@MapperScan 注解核心功能也是基于此类。

在这里插入图片描述

可以看到,MapperScannerConfigurer 实现了BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware 四个接口。这里我们主要看 BeanDefinitionRegistryPostProcessor 的实现方法。
BeanDefinitionRegistryPostProcessorBeanFactory 的后处理器,本文不再解释其用法,具体请看:Spring源码分析七:BeanFactoryPostProcessor 的处理 - invokeBeanFactoryPostProcessors


这里我们直接看 postProcessBeanDefinitionRegistry 方法。

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
	// 为扫描设置各种属性
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
  	// 注册过滤器
    scanner.registerFilters();
    // 开始扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

4.1. processPropertyPlaceHolders();

由于 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 的调用时机要早于 BeanFactoryPostProcessor#postProcessBeanFactory。这就会导致 在执行MapperScannerConfigurer#postProcessBeanDefinitionRegistry 方法时, PropertyResourceConfigurer#postProcessBeanFactory 方法还未执行,属性文件则没有被加载,导致所有对于属性文件的引用将会失效。所以这里为了避免这种情况的发生,选择使用了processPropertyPlaceHolders 方法来手动找出定义的 PropertyResourceConfigurer 并进行提前调用以保证对于属性的引用可以正常工作。


比如:
demo.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="demo" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>mybatis/demo.properties</value>
            </list>
        </property>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="${basePackage}}"/>
    </bean>
</beans>

在 mybatis/demo.properties 文件如下:

basePackage=com.kingfish.springjdbcdemo

但是实际上 ${basePackage} 并没有起到作用,因为在解析 ${basePackage}PropertySourcesPlaceholderConfigurer 还没有被调用,并没有解析属性文件中的内容,所以Spring还不能直接使用。

这一步的作用就是为了解决这种问题。

4.2. 根据配置属性生成过滤器

 public void registerFilters() {
    boolean acceptAllInterfaces = true;

    // if specified, use the given annotation and / or marker interface
    // 处理 annotationClass  属性
    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

    // override AssignableTypeFilter to ignore matches on the actual marker interface
    // 处理 markerInterface  属性
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }
	// 修改 acceptAllInterfaces属性
    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    // package-info.java 的处理
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    });
  }

该方法主要是对 annotationClassmarkerInterface 属性的处理 , 表明扫描过程中只接受标记有注解为 annotationClass 的接口 和 实现了markerInterface 的接口。 如果 annotationClass 和 arkerInterface 有任意一个存在属性,则会将 acceptAllInterfaces 值改变,否则Spring会增加一个默认的过滤器实现 TypeFilter 接口的局部类,接受所有的接口文件。同时对于 package-info.java 命名的java文件,默认不作为逻辑实现接口,将其排除掉。

4.3. 扫描Java 文件

	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

我们这里还是看到 doScan 方法,doScan 的实现类在 ClassPathMapperScanner中,如下。

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	// 调用 ClassPathBeanDefinitionScanner#doScan 方法
    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 {
    // 对 BeanDefinition 进行进一步处理
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

我们这里不再关注 super.doScan 。主要来看看将BeanDefinition 扫描出来后又做了什么,我们来看 processBeanDefinitions 方法的实现。


  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;


  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      // 从这里打印日志可以看到,这里准备创建 MapperFactoryBean 来代理 Mapper 
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      // 设置MapperFactoryBean 的构造参数为 原 Mapper类型
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      // 设置bean为 MapperFactoryBean 类型
      definition.setBeanClass(this.mapperFactoryBeanClass);

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      // 设置MapperFactoryBean 需要 sqlSessionFactory 属性
      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) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      definition.setLazyInit(lazyInitialization);
    }
  }

这里可以看到,将原先的MapperBeanDefinition 全部动态替换成了 MapperFactoryBeanBeanDefinition。比如原先扫描出来的BeanDefinitionUserMapper ,这里会将其替换成 MapperFactorybean<UserMapper> 的BeanDefinition 。由于在 ClassPathBeanDefinitionScanner#doScan 中扫描时就已经注册到BeanFactory 中,这里获取的是BeanDefinition 的引用,即是将容器中的每个Mapper的 BeanDefinition 改为了MapperFactoryBeanBeanDefinition

简单一句话来说,就是在Spring容器中为每个Mapper文件动态注册了MapperFactoryBean。后面获取的Mapper都是进过MapperFactoryBean.getObject中获取。

4.4. Mapper 的封装 - MapperFactoryBean

上面我们讲到,Spring 将所有的Mapper类型封装成了 MapperFactoryBean。那么MapperFactoryBean又做了什么,我们下面来看一下。

在MyBatis单独使用时,调用数据库接口的方式是:

  UserMapper mapper = sqlSession.getMapper(UserMapper.class);

而在Spring中创建的方式却是:

  UserMapper mapper =  (UserMapper)context.getBean("userMapper");
  UserMapper mapper =  context.getBean(UserMapper.class);

Spring 在获取 UserMapper的bean时候其方式和MyBatis 原生方式并不相同,为了完成和单独使用MyBatis 完成了一样的功能。那么Spring必然是在MyBatis 原生基础上再封装了一层。而这一层的封装就在于MapperFactoryBean 。

我们来看 MapperFactoryBean 的声明。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>

在这里插入图片描述

  1. 继承了 SqlSessionDaoSupport 类,这个稍后在看
  2. 实现了 FactoryBean 接口,这个接口的作用请参考:Spring 源码分析衍生篇一:FactoryBean介绍。这里简单提一下,当调用 run.getBean("demoFactoryBean"); 时,Spring通过反射会发现 DemoFactoryBean 实现了FactoryBean接口,则会直接调用 其getObject() 方法,并将方法的返回值注入到Spring容器中。而如果想要获得DemoFactoryBean 实例,则需要在 beanName前加上 & ,即 run.getBean("&demoFactoryBean");**
  3. 由于最上层的DaoSupport 实现了 InitializingBean。我们这里需要看看 afterPropertiesSet 方法。
	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// Let abstract subclasses check their configuration.
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

我们来看一看 MapperFactoryBean 中的 checkDaoConfig 实现

  @Override
  protected void checkDaoConfig() {
  	// 调用了 SqlSessionDaoSupport 的checkDaoConfig。判断了一下 sqlSessionTemplate 是否为空
    super.checkDaoConfig();
	// 这里的mapperInterface 是 Mapper 接口Class 
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
	// 映射文件存在性验证。
    Configuration configuration = getSqlSession().getConfiguration();
    // 如果不存在 xml 映射文件,但是存在 Mapper.java ,则需要将当前的Mapper添加到Mapper容器中
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

这里解释一下关于映射文件存在性的验证,在MyBatis 实现过程中并没有手动调用 configuration.addMapper 方法,而是在映射文件读取过程中一旦解析到 <mapper namespace="...">,便会自动进行类型映射的注册。

在上面的函数中, configuration.addMapper(this.mapperInterface);其实就是将 UserMapper 注册到映射类型中,如果这个接口存在对应的映射文件,那么这一步其实没有什么意义,但是由于这些配置是由我们自行决定配置,无法保证这里的配置的接口一定存在对应的映射文件,所以这里的验证非常有必要。在执行此代码的时候,MyBatis 会检查嵌入的映射接口是否存在对应的映射文件,如果没有则抛出异常。Spring 正是在用这种方式来完成接口对应的映射文件存在性验证。

getObject 的实现很简单,就是对 原生MyBatis 封装了一层。

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

也就是说,对于每一个Mapper, 注入到Spring容器中的都是对应的MapperFactoryBean。在获取Mapper的时候会获取到对应的 MapperFactorybean随后调用其getObject ,将其返回值作为bean返回。


对于FactoryBean的使用,我到现在还没有完全理解其使用意义。所以以下是个人的推测
Mapper 文件使用MapperFactoryBean 有两点考量:

  • 在 Spring 中使用MyBatis 可以仅仅定义Mapper 接口而不定义xml,而MyBatis 是通过读取xml 的方式来注入Mapper(读取到XML后,通过 <mapper namespace="..."> 找到对应的Mapper接口,从而注册到SqlSession 中)。而如果我们不定义XML,那么会出现MyBatis 无法感知到 Mapper 接口,无法保存到SqlSession 中。为了使MyBatis 可以感知到Mapper接口,在每个Mapper创建后需要检查当前Mapper是否已经在 SqlSession中已经注册,如果没有注册,则进行一次注册。而通过 MapperFactoryBean,在每个MapperFactoryBean 创建的时候,我们都可以来检测Mapper是否已经注册。
  • MyBatis 对Mapper的创建是通过代理实现,这一部分MyBatis 已经实现,即我们获取到的Mapper实际上是一个代理类。试想一下,Spring如果不通过MapperFactoryBean 来从sqlSession 中获取Mapper代理,那么就需要自己重写一遍 Mapper的代理过程,这是不可取的。所以Spring通过 MapperFactoryBean 做了一层代理,通过MapperFactoryBean 从SqlSession 中获取 Mapper文件,将Mapper 代理创建交由MyBatis 来创建。

一次讲透彻 beanFactory和factoryBean 中描述FactoryBean的一句话:
总结来说,就是一种方法,要么是解决生成BeanDefinition的困难;要么是解决反射实例化bean时候遇到的困难,将spring不好自动化处理的逻辑写到getObject方法内。


以上:内容部分参考
《Spring源码深度解析》
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值