Spring source code analysis 10: automatic configuration of Mybatis in spring boot

Article catalog

1, Foreword

This article is the author's record of reading Spring source code. Due to my limited technical level, errors are inevitable in the article. If you find any, thank you for your correction. In the process of reading, some derivative articles are also created. The significance of derivative articles is that in the process of reading the source code, some knowledge points do not understand or have interest in some knowledge points. Therefore, in order to better read the source code, a derivative article is set up to better learn these knowledge points.

This article is not about MyBatis source code!!!! Instead, it explains how to implement the automatic assembly process of MyBatis in spring boot.

Because Mybatis does not want other integrations such as AspectJ to have a startup annotation @ EnableAspectJAutoProxy for us to analyze as a breakthrough point. Then we can see his source code.

From the pom file, we introduced

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

We can speculate that the content of automatic configuration must be this.
We found a class MybatisAutoConfiguration in the source code. As you can see from the name, this class must be the entry for automatic configuration of Mybatis.

From spring.factories As you can see in the file, MybatisAutoConfiguration is an automatic configuration

2, MybatisAutoConfiguration

Let's first look at the structure of the MybatisAutoConfiguration class. The following code is simplified.

@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.");
    }

  }
}

From the above we can learn that MybatisAutoConfiguration knows the following things:

  1. As a configuration class, MybatisAutoConfiguration depends on SqlSessionFactory, SqlSessionFactoryBean, DataSource, DataSourceAutoConfiguration, MybatisLanguageDriverAutoConfiguration and other classes. At the same time, MybatisProperties is introduced as MyBatis related configuration class. At the same time, the InitializingBean interface is implemented, and the afterpropertieset method is used for callback when creating.
  2. If SqlSessionFactory is not injected elsewhere, MybatisAutoConfiguration will inject. What's injected here is through s qlSessionFactoryBean.getObject Obtained, which will be explained later.
  3. If SqlSessionTemplate is not injected elsewhere, MybatisAutoConfiguration will inject
  4. If MapperFactoryBean and MapperScannerConfigurer are not injected in other places, MybatisAutoConfiguration will inject and introduce the autoconfiguredmappperscannerregistrar class.

We focus on the following points.

1. Declaration of 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 {

Mybatis autoconfiguration has a bunch of comments on it.

  • @Configuration: declare this class as a configuration class, needless to say
  • @ConditionalOnClass({ SqlSessionFactory.class S qlSessionFactoryBean.class }): indicates that sqlsessionfactory and SqlSessionFactoryBean will be injected into the container. Here, sqlsessionfactory class is the basis of MyBatis' function, while SqlSessionFactoryBean implements the FactoryBean interface, which is also suitable for Spring to obtain sqlsessionfactory.
  • @ConditionalOnSingleCandidate( DataSource.class ): indicates that the container is only injected when there is a single instance of datasource in the container
  • @EnableConfigurationProperties( MybatisProperties.class ): enables configuration properties. Related configuration information of Mybatis is saved in mybatisproperties
  • @AutoConfigureAfter({ DataSou rceAutoConfiguration.class , MybatisLanguageDri verAutoConfiguration.class }): the function is to control the loading order of configuration classes. Load this class after DataSourceAutoConfiguration and mybatislanguagedriveraautoconfiguration are loaded.

2. SqlSessionFactory injection

Mybatis can be configured through xml configuration class, but in spring boot, we can also configure directly through yml configuration file (yml here is just one of the spring boot configuration files, which does not mean that only yml can be used. The same below, yml represents the configuration file configuration of spring boot), and the following two configurations are equivalent.

The yml configuration mode in Spring boot is not provided by Mybatis, which requires Spring to implement by itself. So Spring implements SqlSessionFactoryBean to encapsulate SqlSessionFactory.

Let's first look at the injection of sqlSessionFactory in MybatisAutoConfiguration. You can see here that sqlSessionFactory is injected through s qlSessionFactoryBean.getObject Implemented. SqlSessionFactoryBean saves various properties and returns the encapsulated sqlSessionFactory through GetObject.

Note: there are two configurations for Mybatis: one is through xml, which requires the path of xml, that is, ConfigLocation.
Another kind of yml configuration, like the one above, needs to save its configuration properties, that is, configuration properties

@Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    // If the configuration file address config location is configured, save it
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    applyConfiguration(factory);
    // If the configuration property is configured, save the property
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    // The same logic below saves various configurations.
    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);
    }
	// Get SqlSessionFactory 
    return factory.getObject();
  }

So let's look at s here qlSessionFactoryBean.getObject How to initialize SqlSessionFactory:

As you can see, when getObject is used, whether sqlSessionFactory is initialized will be judged. Otherwise, sqlSessionFactory will be initialized

  @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;
    // If the yml configuration is used. Then use yml configuration
    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);
      }
      // If a profile is specified, the profile's configuration is used.
    } 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);
	// Let's do a lot of attribute assignment, and we won't go over it
    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);
  }

As can be seen above:

  1. Spring boot configuration mode takes precedence over XML configuration mode
  2. Spring boot encapsulates the SqlSessionFactoryBean mode on the basis of Mybatis to adapt to the configuration of spring boot configuration file mode.

3. Injection of autoconfiguredmapperscannerregistrar

The above step adapts to the configuration mode of spring boot.

However, MyBatis itself provides an @ Mapper annotation. The annotated interface will be injected into the Spring container and mapped with Mapper file one by one. However, Spring does not support the scanning of @ Mapper annotation. Then @ Mapper's work needs MyBatis to complete by itself. The completion of this step is in the AutoConfiguredMapperScannerRegistrar class.

Autoconfigured mapperscannerregistrar was introduced in MapperScannerRegistrarNotFoundConfiguration through @ Import annotation in the declaration.

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

Let's see the statement of autoconfiguredmapperscannerregistration, which implements beanfactoryaware (beanfactory can be obtained), and implements the importbeandefinitionregister interface. We can complete the BeanDefinition registration, modification and other operations through the registerBeanDefinitions method provided by this interface.

 public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

      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");
		// Get the path of the BeanFactory scan
      List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
      if (logger.isDebugEnabled()) {
        packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
      }

      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
      builder.addPropertyValue("processPropertyPlaceHolders", true);
      // Set annotation @ Mapper to be scanned
      builder.addPropertyValue("annotationClass", Mapper.class);
      // Set scan path
      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}"));
      // Register the Bean definition of the scanned Bean 
      registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }

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

  }

The logic is very simple. Spring doesn't scan @ Mapper for me. I can scan @ Mapper annotation injection for myself.

4. Encapsulation of mypper MapperFactoryBean

When MyBatis is used alone, the way to call the database interface is:

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

The way to create it in Spring is:

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

Spring does not get the bean of UserMapper in the same way as the native way of MyBatis. In order to complete the same function as using MyBatis alone. Then spring must be a layer of encapsulation based on MyBatis native. The encapsulation of this layer is MapperFactoryBean.

Let's look at MapperFactoryBean's declaration.

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

  1. Inherited SqlSessionDaoSupport class, which will be seen later
  2. The FactoryBean interface is implemented. Please refer to: Spring source code analysis derivation part 1: FactoryBean introduction . Let's briefly mention that when you call run.getBean("demoFactoryBean"); when Spring finds that DemoFactoryBean implements FactoryBean interface through reflection, it will directly call its getObject() method and inject the return value of the method into the Spring container. If you want to get an instance of DemoFactoryBean, you need to add &, which is run.getBean ("&DemoFactoryBean");**
  3. Because the top-level DaoSupport implements InitializingBean. We need to look at the afterpropertieset method here.
	@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);
		}
	}

Let's take a look at the implementation of checkDaoConfig in MapperFactoryBean

  @Override
  protected void checkDaoConfig() {
  	// checkDaoConfig of SqlSessionDaoSupport was called. Judge whether sqlSessionTemplate is empty
    super.checkDaoConfig();
	// Mappinterface here is Mapper interface Class 
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
	// Mapping file existence verification.
    Configuration configuration = getSqlSession().getConfiguration();
    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();
      }
    }
  }

The verification of the existence of the mapping file is explained here. There is no manual call during the implementation of MyBatis configuration.addMapper Method, once the < mapper namespace = "..." > is parsed during the reading of the mapping file, the type mapping registration will be automatically performed.

In the above function, configuration.addMapper(this.mapperInterface); In fact, it is to register UserMapper into the mapping type. If there is a corresponding mapping file for this interface, then this step is meaningless. However, since these configurations are determined by ourselves, there is no guarantee that there will be a corresponding mapping file for the configured interface, so the verification here is very necessary. When executing this code, MyBatis will check whether there is a corresponding mapping file in the embedded mapping interface, and throw an exception if not. Spring is using this method to verify the existence of the mapping file corresponding to the interface.

The implementation of getObject is very simple. It encapsulates a layer of native MyBatis.

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

That is to say, for each mapper, the MapperFactorybean is injected into the Spring container. When you get Mapper, you will get the corresponding MapperFactorybean, then call its getObject, and return the value as bean.

3, MapperScannerConfigurer

In the above statement of Autoconfigured mapperscannerregistration, we see that mapperscannerregistratirnotfoundconfiguration needs to be injected when mapperfactorbean and MapperScannerConfigurer do not exist, MapperScannerConfigurer is injected in Autoconfigured mapperscannerregistration, and the @ Mapper annotation scanning is completed. Let's take a look at the implementation of MapperScannerConfigurer.

Note that the @ MapperScan annotation core function is also based on this class.

As you can see, MapperScannerConfigurer implements four interfaces: BeanDefinitionRegistryPostProcessor, initializingbean, applicationcontextaware and beannameaware. Here we mainly look at the implementation of BeanDefinitionRegistryPostProcessor.
BeanDefinitionRegistryPostProcessor is the postprocessor of BeanFactory. Its usage will not be explained in this article. For details, see: Spring source code analysis 7: BeanFactoryPostProcessor processing - invokebenfactorypostprocessors

Let's look directly at the postProcessBeanDefinitionRegistry method.

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
	// Set various properties for the scan
    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));
    }
  	// Register filter
    scanner.registerFilters();
    // Start scan
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

1. processPropertyPlaceHolders();

Because beandefinitionregistrypostprocessor ා postprocessbeandefinitionregistry is called earlier than beanfactorypostprocessor ා postprocessbeanfactory. This will cause the propertyresourceconfigurer postprocessbeanfactory method to not execute when mapperscannerconfigurer ා postprocessbeandefinitionregistry method is executed, and the property file is not loaded, which will cause all references to the property file to be invalid. So in order to avoid this happening, choose processPropertyPlaceHolders method to manually identify the defined PropertyResourceConfigurer and make an early call to ensure that the reference to the attribute can work normally.

For example:
demo.xml as follows

<?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>

In mybatis/demo.properties The documents are as follows:

basePackage=com.kingfish.springjdbcdemo

But actually ${basePackage} doesn't work, because when parsing ${basePackage}, PropertySourcesPlaceholderConfigurer hasn't been called, and the content in the property file hasn't been parsed, so Spring can't be used directly.

The purpose of this step is to solve this problem.

2. Generate filter according to configuration attribute

 public void registerFilters() {
    boolean acceptAllInterfaces = true;

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

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

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

This method mainly deals with the attributes of annotationClass and markerInterface. It shows that only interfaces marked with annotation as annotationClass and interfaces of markerInterface are accepted in the scanning process. If either annotationClass or arkerInterface has an existing attribute, the value of acceptAllInterfaces will be changed. Otherwise, Spring will add a default filter to implement the local class of TypeFilter interface and accept all interface files. And for package-info.java The named java file, by default, is not used as a logical implementation interface and is excluded.

3. Scan Java files

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);
	}

We still see the doScan method here. The implementation class of doScan is in ClassPathMapperScanner, as follows.

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	// Call classpathbeandefinitionscanner - doscan method
    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 {
    // Further processing of BeanDefinition
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

We don't pay attention here super.doScan . Mainly to see what has been done after scanning BeanDefinition. Let's see the implementation of processbeandefinition method.

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      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
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBeanClass);

      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) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      definition.setLazyInit(lazyInitialization);
    }
  }

As you can see here, the original Mapper's BeanDefinition is all dynamically replaced with MapperFactoryBean's BeanDefinition. For example, the previously scanned BeanDefinition is UserMapper, which will be replaced by the BeanDefinition of mapperfactory bean < UserMapper >. Since it has been registered in BeanFactory when scanning in classpathbeandefinitionscanner, the reference of BeanDefinition obtained here is to change BeanDefinition of every Mapper in the container to BeanDefinition of MapperFactoryBean.

To put it simply, mapperfactorybean is dynamically registered in the Spring container for each Mapper file. Mappers obtained later are all imported MapperFactoryBean.getObject Obtained from.

Above: content part reference
Deep analysis of Spring source code
If there is any intrusion, please contact to delete. The content is only used for self recording learning. If there is any mistake, please correct it

Tags: Spring Mybatis Java xml

Posted on Sun, 14 Jun 2020 05:41:37 -0400 by labourstart