Mybatis automatic assembly source code analysis

1. Springboot integrates mybatis

pom import

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

The role of the starter project of mybatis is only to import related dependencies. There is no code in this project, only the pom file

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
  </dependency>
</dependencies>

The package that actually handles autowiring is mybatis-spring-boot-autoconfigure

2. Mybatis auto-assembles the core JAR package: mybatis-spring-boot-autoconfigure

There is a spring.factories file under the META-INF package (the key to automatic assembly of springboot, which will be explained in detail in subsequent articles)

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

There are only 5 classes under this package, they are

ConfigurationCustomizer MybatisAutoConfiguration MybatisLanguageDriverAutoConfiguration MybatisProperties SpringBootVFS

In these 5, we only need to pay attention to the two classes MybatisAutoConfiguration and MybatisProperties

Three, MybatisProperties configuration file

The essence of this class is to configure mybatis-related properties, which are configured in the application.yml or application.properties file

@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
    
    

  public static final String MYBATIS_PREFIX = "mybatis";

  private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();

  
  private String configLocation;

  private String[] mapperLocations;

  private String typeAliasesPackage;

  private Class<?> typeAliasesSuperType;

  private String typeHandlersPackage;

  private boolean checkConfigLocation = false;

  private ExecutorType executorType;
}

Fourth, the core class of automatic assembly - MybatisAutoConfiguration

1. Preconditions for assembly

@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 {
    
    
}

@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) ensures that the jars related to mybatis and spring-mybatis are introduced

@ConditionalOnSingleCandidate(DataSource.class) ensures that the DataSource singleton object in the container or has a designated main implementation class

@EnableConfigurationProperties(MybatisProperties.class) ensures that the MybatisProperties.class configuration object is loaded

@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) Assembles the current class after the container assembles DataSourceAutoConfiguration.class

2. Bean initialization method execution

The function is to judge whether the xml file of mybatis is used, and to verify whether the file exists

@Override
public void afterPropertiesSet() {
    
    
  checkConfigFileExists();
}
private void checkConfigFileExists() {
    
    
  if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
    
    
    Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
    Assert.state(resource.exists(),
        "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
  }
}

3. Build SqlSessionFactory of mybatis

@ConditionalOnSingleCandidate(DataSource.class) in the conditional assembly on the class ensures that the DataSource must already exist in the container

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    
    
  SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  factory.setDataSource(dataSource);
  factory.setVfs(SpringBootVFS.class);
  if (StringUtils.hasText(this.properties.getConfigLocation())) {
    
    
    factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
  }
  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);
  }

  return factory.getObject();
}

4. Build SqlSessionTemplate

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    
    
  ExecutorType executorType = this.properties.getExecutorType();
  if (executorType != null) {
    
    
    return new SqlSessionTemplate(sqlSessionFactory, executorType);
  } else {
    
    
    return new SqlSessionTemplate(sqlSessionFactory);
  }
}

5. Scan Mapper

A very important point here is that@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })

In order to prevent double registration after using the **@MapperScan annotation, because the @MapperScan** annotation mentioned in the previous chapter will trigger the creation of MapperFactoryBean and MapperScannerConfigurer

ps: The role of the @ConditionalOnMissingBean annotation is to load the current class when the specified class instance does not exist in the container

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

}

In the annotation on the class, the key to registering the Mapper is that @Import(AutoConfiguredMapperScannerRegistrar.class)the annotation will trigger the instantiation of the AutoConfiguredMapperScannerRegistrar

6, AutoConfiguredMapperScannerRegistrar object instantiation

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

    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);
    builder.addPropertyValue("annotationClass", Mapper.class);
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
    BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
    Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    if (propertyNames.contains("lazyInitialization")) {
    
    
      // Need to mybatis-spring 2.0.2+
      builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
    }
    if (propertyNames.contains("defaultScope")) {
    
    
      // Need to mybatis-spring 2.0.6+
      builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
    }
    registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
  }

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

}

The registration logic here is similar to the instantiation of MapperScannerRegistrar triggered by MapperScan in mybatis-spring.注册MapperScannerConfigurer对象来触发Mapper注册到IOC容器

There are two key points of difference

1. Which packages should be scanned for classes

2. What kind of subclass to scan

① Scanned packages

Get packages by getting the package path of the startup class

List<String> packages = AutoConfigurationPackages.get(this.beanFactory);

The source of the beanFactory is injected by implementing the implements BeanFactoryAware interface

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

② scan what kind of class

In this automatic assembly mode, the Mapper that needs to be injected needs to be annotated with @Mapper. If it is not added, it cannot be registered in the IOC container.

builder.addPropertyValue("annotationClass", Mapper.class);

V. Summary

1. Mybatis automatically assembles the essence of scanning Mapper

Whether it is the @MapperScan method in the previous mybatis-spring or the springboot automatic assembly method, the MapperScannerConfigurer object is actually injected into the IOC container, thereby triggering the logic of scanning Mapper injection inside the MapperScannerConfigurer

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    
    
  // 扫描Mapper
}

2. Use of @MapperScan and @Mapper

For some time before, I didn't know how to use these two annotations together. Later, I knew how to use them through the source code, so I recommend that you look at the source code more.

The @MapperScan annotation configures the scan mapper package

In the case of @Mapper auto-assembly, the scan mapper starts from the startup class package, so use @Mapper to mark the current class as the Mapper that needs to be injected

Guess you like

Origin blog.csdn.net/weixin_33613947/article/details/110558378