How to integrate Spring Mybatis, the source code is not difficult thing!

Spring integration Mybtais will be configured as follows (all roads lead to Rome, not the only way).

private static final String ONE_MAPPER_BASE_PACKAGE = "com.XXX.dao.mapper.one";
@Bean
public MapperScannerConfigurer oneMapperScannerConfigurer() {
    MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
    mapperScannerConfigurer.setBasePackage(ONE_MAPPER_BASE_PACKAGE);
    mapperScannerConfigurer.
                setSqlSessionFactoryBeanName("oneSqlSessionFactoryBean");
    return mapperScannerConfigurer;
}
@Primary
@Bean(name="oneSqlSessionFactoryBean")
public SqlSessionFactoryBean oneSqlSessionFactoryBean( @Qualifier("oneDataSource") DruidDataSource oneDataSource) {
    return getSqlSessionFactoryBeanDruid(oneDataSource,ONE_MAPPER_XML);
}
复制代码

In less than 20 lines of code, to complete the integration of Spring Mybatis.

Amazing! ! ! Behind in the end what happened?

We should proceed from MapperScannerConfigurer and SqlSessionFactoryBean.

MapperScannerConfigurer

Class Notes

  • beanDefinitionRegistryPostProcessor from the base package recursive search interface, they will be registered as MapperFactoryBean. Note interface must include at least a method implementation class will be ignored.

  • 1.0.1 before BeanFactoryPostProcessor is extended, after 1.0.2 is BeanDefinitionRegistryPostProcessor be extended, please refer to the specific reasons https://jira.springsource.org/browse/SPR-8269

  • basePackage can configure multiple, divided using commas or semicolons.

  • Or by annotationClass markerInterface, specify the scan interface may be provided. The two attributes is empty, all the interfaces under basePackage be scanned by default.

  • MapperScannerConfigurer bean is that it creates an automatic injection or SqlSessionFactory SqlSessionFactory SqlSessionTemplate If there are multiple, or to set sqlSessionFactoryBeanName sqlSessionTemplateBeanName specify particular injected or sqlSessionFactory sqlSessionTemplate.

  • There can not pass the object placeholder (for example: an object database that contains the user name and password placeholder). You can use beanName, creates postponed until after completion of all the placeholder with the actual object. Note MapperScannerConfigurer support its own properties with a placeholder, $ {property} using the formats.

The key method to find the class diagram

MapperScanConfigurer
MapperScanConfigurer

From the class chart MapperScannerConfigurer realized BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware interface. Each interface explained as follows:

  • ApplicationContextAware: After initialization spring containers, automatically injected ApplicationContext
  • BeanNameAware: Set the current name in Spring Bean
  • InitializingBean interfaces include only afterPropertiesSet method will be executed when the bean initialization
  • BeanDefinitionRegistryPostProcessor: BeanFactoryPostProcessor extension to allow the definition of multiple registration bean before BeanFactoryPostProcessor execution. We need to expand the way for postProcessBeanDefinitionRegistry.

Query, MapperScannerConfigurer of afterPropertiesSet follows no specific extension information.

@Override public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required"); 
}
复制代码

Notes binding MapperScannerConfigurer class diagram and analysis method to determine the core: postProcessBeanDefinitionRegistry

postProcessBeanDefinitionRegistry analysis

@Override
public void postProcessBeanDefinitionRegistry(
                    BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
      //1. 占位符属性处理
    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);
  //2.设置过滤器
  scanner.registerFilters();
  //3.扫描java文件
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, 
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
复制代码

Seen from the source in addition to processPropertyPlaceHolders, other work commissioned ClassPathMapperScanner

processPropertyPlaceHolders processing placeholder

Before BeanFactoryPostProcessor said BeanDefinitionRegistryPostProcessor called before execution,

This means that Spring placeholder class PropertyResourceConfigurer processing has not been executed!

How is the support that MapperScannerConfigurer own properties using placeholders it? All answers are

processPropertyPlaceHolders this method.

private void processPropertyPlaceHolders() {
  Map<String, PropertyResourceConfigurer> prcs =
       applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
  if (!prcs.isEmpty() && applicationContext 
                      instanceof GenericApplicationContext) {
    BeanDefinition mapperScannerBean = 
            ((GenericApplicationContext) applicationContext)
                        .getBeanFactory().getBeanDefinition(beanName);
    // PropertyResourceConfigurer 没有暴露方法直接替换占位符,
    // 创建一个 BeanFactory包含MapperScannerConfigurer
    // 然后执行BeanFactory后处理即可
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    factory.registerBeanDefinition(beanName, mapperScannerBean);

    for (PropertyResourceConfigurer prc : prcs.values()) {
      prc.postProcessBeanFactory(factory);
    }
    PropertyValues values = mapperScannerBean.getPropertyValues();
    this.basePackage = updatePropertyValue("basePackage", values);
    this.sqlSessionFactoryBeanName =
            updatePropertyValue("sqlSessionFactoryBeanName", values);
    this.sqlSessionTemplateBeanName = 
            updatePropertyValue("sqlSessionTemplateBeanName", values);
  }
}
复制代码

After reading processPropertyPlaceHolders, can be summarized MapperScannerConfigurer support its own properties using placeholders way

  1. Find all registered PropertyResourceConfigurer type of Bean

  2. Use new DefaultListableBeanFactory () Spring simulated environment, this will be registered to the BeanFactory MapperScannerConfigurer, BeanFactory performed post-processing, to replace the placeholder.

ClassPathMapperScanner method of registerFilters

Type of annotation MapperScannerConfigurer have a:

Or by annotationClass markerInterface, specify the scan interface may be provided, the default attribute 2 is empty, all the interfaces under basePackage to be scanned. scanner.registerFilters (), is set annotationClass and markerInterface of.

public void registerFilters() {
  boolean acceptAllInterfaces = true;

  // 如果指定了annotationClass,
  if (this.annotationClass != null) {
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
    acceptAllInterfaces = false;
  }
  // 重写AssignableTypeFilter以忽略实际标记接口上的匹配项
  if (this.markerInterface != null) {
    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
      @Override
      protected boolean matchClassName(String className) {
        return false;
      }
    });
    acceptAllInterfaces = false;
  }

  if (acceptAllInterfaces) {
    // 默认处理所有接口
    addIncludeFilter(new TypeFilter() {
      @Override
      public boolean match(
      MetadataReader metadataReader, 
      MetadataReaderFactory metadataReaderFactory) throws IOException {
        return true;
      }
    });
  }

  // 不包含以package-info结尾的java文件
  // package-info.java包级文档和包级别注释
  addExcludeFilter(new TypeFilter() {
    @Override
    public boolean match(MetadataReader metadataReader, 
    MetadataReaderFactory metadataReaderFactory) throws IOException {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    }
  });
}
复制代码

Although the filter set, depends on how it works scanner.scan the scan method.

ClassPathMapperScanner the scan method

public int scan(String... basePackages) {
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
   doScan(basePackages);
   // 注册注解配置处理器
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils
                  .registerAnnotationConfigProcessors(this.registry);
   }
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
复制代码

doScan follows:

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 {
    processBeanDefinitions(beanDefinitions);
  }
  return beanDefinitions;
}
复制代码

doScan method ClassPathBeanDefinitionScanner located ClassPathMapperScanner parent class, that is,

Java files scan all packets into the BeanDefinition (actually ScannedGenericBeanDefinition).

processBeanDefinitions is to convert before BeanDefinition to MapperFactoryBean of BeanDefinition.

As for how to filter into force (ie annotationClass or markerInterface) it? I tracked all the way Source

Finally found the handle of the filter in ClassPathScanningCandidateComponentProvider of isCandidateComponent

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
   for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, this.metadataReaderFactory)) {
         return false;
      }
   }
   for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, this.metadataReaderFactory)) {
         return isConditionMatch(metadataReader);
      }
   }
   return false;
}
复制代码

Summary of action MapperScannerConfigurer

MapperScannerConfigurer realized beanDefinitionRegistryPostProcessor method of postProcessBeanDefinitionRegistry

BasePackage from the specified directory recursively search interface, they will be registered as MapperFactoryBean

SqlSessionFactoryBean

Class Notes

  1. Creating SqiSessionFactory Mybatis for the Spring context to share.

  2. SqiSessionFactory daos and may be injected into the through mybatis dependent.

  3. datasourcetransactionmanager, jtatransactionmanager want to combine with sqlsessionfactory implement transactions.

The key method to find the class diagram

sqlSessionFactoryBean
sqlSessionFactoryBean类图

SqlSessionFactoryBean achieved ApplicationListener, InitializingBean, FactoryBean interfaces, each interface described as follows:

  • ApplicationListener event listener for the Spring
  • InitializingBean interfaces include only afterPropertiesSet method will be executed when the bean initialization
  • FactoryBean: not specify a returned object instance of the class, which returns the object getObject method returned by the FactoryBean

We should focus afterPropertiesSet and getObject method.

The key method of analysis

afterPropertiesSet method

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();
}
复制代码

buildSqlSessionFactory see the method name to know in here for a SqlSessionFactory creation of specific sources are not repeated here.

getObject method

public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }
  return this.sqlSessionFactory;
}
复制代码

Summary SqlSessionFactoryBean

Realized InitializingBean of afterPropertiesSet, in which to create a Mybatis of SqlSessionFactory

The getObject returns achieved FactoryBean create good sqlSessionFactory.

doubt

After reading this SqlSessionFactoryBean and MapperScannerConfigurer, I do not know if you have any questions! Mybatis generally used in the Spring of the following ways:

ApplicationContext context=new AnnotationConfigApplicationContext();
UsrMapper  usrMapper=context.getBean("usrMapper");
实际上调用的是
sqlSession.getMapper(UsrMapper.class);
复制代码

SqlSessionFactoryBean created Mybatis of SqlSessionFactory. MapperScannerConfigurer the interface converter to MapperFactoryBean. Where is that they call sqlSession.getMapper (UsrMapper.class) it? ? ?

MapperFactoryBean is the answer to all this (MapperFactoryBean: look at my name --- Mapper factory !!)

Description MapperFactoryBean

Class Notes

It can be injected BeanFactory MyBatis mapping interface. It may be provided SqlSessionFactory SqlSessionTemplate or preconfigured.
Note that this interface does not inject injection plant only implementation class

The key method to find the class diagram

Folders Factory Bean
Folders Factory Bean

See the class diagram, and saw the InitializingBean and FactoryBean! ! !

  • InitializingBean interfaces include only afterPropertiesSet method will be executed when the bean initialization
  • FactoryBean: not specify a returned object instance of the class, which returns the object getObject method returned by the FactoryBean

AfterPropertiesSet and realize once again focus on the getObject!

The key method of analysis

DaoSupport afterPropertiesSet class has implemented as follows:

public final void afterPropertiesSet()
     throws IllegalArgumentException, BeanInitializationException {
    this.checkDaoConfig();
    try {
        this.initDao();
    } catch (Exception var2) {
        throw
         new BeanInitializationException(
                             "Initialization of DAO failed",  var2);
    }
}
复制代码

initDao is empty implementation, checkDaoConfig for realizing the MapperFactoryBean follows:

protected void checkDaoConfig() {
  super.checkDaoConfig();

  notNull(this.mapperInterface, "Property 'mapperInterface' is required");

  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 key statement is configuration.addMapper (this.mapperInterface), add the interface to Mybatis configuration.

getObject method super simple, it is called sqlSession.getMapper (UsrMapper.class);

public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface); 
}
复制代码

Summary MapperFactoryBean

AfterPropertiesSet achieved InitializingBean the method, in which the mapper interface to the mybatis configuration.

FactoryBean realized the getObject method, called sqlSession.getMapper, return mapper object.

to sum up

Spring Integration Mybatis core three categories:

MapperScannerConfigurer

BeanDefinitionRegistryPostProcessor realized postProcessBeanDefinitionRegistry method in which a recursive search interface basePackage from the specified directory, they will be registered as MapperFactoryBean type of BeanDefinition

SqlSessionFactoryBean

Realized InitializingBean of afterPropertiesSet, in which the creation of a SqlSessionFactory Mybatis.

The getObject returns achieved FactoryBean create good sqlSessionFactory.

Folders Factory Bean

AfterPropertiesSet achieved InitializingBean the method, the interface to the mapper mybatis configuration.

FactoryBean realized the getObject method, called sqlSession.getMapper, return mapper object.

Guess you like

Origin juejin.im/post/5dbe9b0be51d452a066994e5