Mybatis and Spring integrate some source code analysis

1. @MapperScan annotation analysis

@MapperScan(basePackages = "com.ziroom.springboot.springbootsourcetest.mapper")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    
    

The MapperScan annotation contains the @Import annotation, which is used to load the MapperScannerRegistrar object

@Import is very important in springboot, the main function is to load the specified class into the spring container

MapperScannerRegistrar, java also implements the ImportBeanDefinitionRegistrar interface. The implementation method of this interface is used to register BeanDefinitions. With the @Import annotation, the method registerBeanDefinitions of the ImportBeanDefinitionRegistrar interface will be called.

2. Implementation of MapperScannerRegistrar

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) {
    
    
    registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
        generateBaseBeanName(importingClassMetadata, 0));
  }
}

This part is mainly to get the annotation **@MapperScan**

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
    BeanDefinitionRegistry registry, String beanName) {
    
    
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  ...  //省略部分代码
    // 2、设置属性
    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
    
    
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }
  
    //3、设置扫描的包
    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));
  
  	//4、当前注解所在的包路径
  	if (basePackages.isEmpty()) {
    
    
      basePackages.add(getDefaultBasePackage(annoMeta));
    }
    
    // 5、将当前封装的BeanDefinition对象注册
	  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
  
}

BeanDefinitionBuilderThis object is the description of the object in spring and contains all the information of the object

1. Create the BeanDefinitionBuilder object of the MapperScannerConfigurer object, which contains all the configuration information of mybatis and integrated spring

basePackage

sqlSessionFactory

sqlSessionTemplate

2. AnnotationMetadata AnnoMeta is the encapsulation of the annotation MapperScan. Many of the following codes encapsulate the data of annoMeta into the BeanDefinitionBuilder object, and then assign attributes when instantiating the MapperScannerConfigurer object.

3. Set the package scanned by mapper

4. If the scanned package path is not set in the annotation @MapperScan, the path of the package where the annotation is located will be used by default.

5. Register the currently encapsulated BeanDefinition object

3. BeanDefinition loading of MapperScannerConfigurer

This class implements the postProcessBeanDefinitionRegistry method of the interface BeanDefinitionRegistryPostProcessor, which is executed after the BeanDefinition is registered

@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);
  
  ...
    
  // 关键步骤,对mapper进行扫描
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

This method is mainly to build a ClassPathMapperScanner object and scan the specified mapper. The key step here is the last step

4. Scan mapper–ClassPathMapperScanner

BeanDefinitionHolderThe wrapper class of BeanDefinition, which contains BeanDefinition and BeanName information

1. The class ClassPathMapperScanner inherits the spring class ClassPathBeanDefinitionScanner and rewrites the doScan method

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    
    
  // 调用的是父类ClassPathBeanDefinitionScanner的方法
  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;
}

2. When calling doScan of the parent class, all mappers will be scanned, and these mappers will be assembled into a BeanDefinitionHolder. While returning the collection, the BeanDefinition will generate a proxy object and register it in the container (this is not an instance, or a BeanDefinition object)

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    
    
  
  			...  省略部分代码		
  
				if (checkCandidate(beanName, candidate)) {
    
    
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
          // 代理对象
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
          // 注册
					registerBeanDefinition(definitionHolder, this.registry);
				}
	  return beanDefinitions;
}

Finally, return the BeanDefinitionHolder collection of all mappers scanned

3. Make several rounds of jump calls here to get the proxy object, and enter the AOP related proxy package

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) {
    
    
    String originalBeanName = definition.getBeanName();
    BeanDefinition targetDefinition = definition.getBeanDefinition();
    String targetBeanName = getTargetBeanName(originalBeanName);
    RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
    proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
    proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
    proxyDefinition.setSource(definition.getSource());
    proxyDefinition.setRole(targetDefinition.getRole());
    proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
    if (proxyTargetClass) {
    
    
        targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
    } else {
    
    
        proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
    }

    proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
    proxyDefinition.setPrimary(targetDefinition.isPrimary());
    if (targetDefinition instanceof AbstractBeanDefinition) {
    
    
        proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition)targetDefinition);
    }

    targetDefinition.setAutowireCandidate(false);
    targetDefinition.setPrimary(false);
    registry.registerBeanDefinition(targetBeanName, targetDefinition);
    return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}

4. After returning the BeanDefinitionHolder collection of the scanned mapper, inject and configure each BeanDefinitionHolder, because only the interface-related information is recorded before this, and the implementation class and some attribute values ​​need to be injected.

private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    
    
  AbstractBeanDefinition definition;
  BeanDefinitionRegistry registry = getRegistry();
  for (BeanDefinitionHolder holder : beanDefinitions) {
    
    
    definition = (AbstractBeanDefinition) holder.getBeanDefinition();
    boolean scopedProxy = false;

    String beanClassName = definition.getBeanClassName();
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    definition.setBeanClass(this.mapperFactoryBeanClass);  // 设置实现类
    definition.setLazyInit(lazyInitialization);
    if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
    
    
        definition.setScope(defaultScope);
      }
   }
}

The key step is to set BeanClass to mapperFactoryBeanClass

Five, Mapper instantiation

When the interface proxy object is instantiated, it is actually the instantiated MapperFactoryBean. The top-level parent class of this class is spring's DaoSupport, which implements the InitializingBean interface, that is, the afterPropertiesSet method is executed when the object is instantiated.

public abstract class DaoSupport implements InitializingBean {
    
    
    protected final Log logger = LogFactory.getLog(this.getClass());

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

    protected abstract void checkDaoConfig() throws IllegalArgumentException;

    protected void initDao() throws Exception {
    
    
    }
}

Here, the checkDaoConfig method is called through the template mode

The checkDaoConfig method is implemented in the MapperFactoryBean class. Here, the scanned mapper interface is added to myabtis.

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

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

  Configuration configuration = getSqlSession().getConfiguration();
  if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
    
    
    try {
    
    
      // 添加mapper
      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 class object scanned by this.mapperInterface

6. Summary

The integration of mybatis and spring is actually handing over the mapper to spring management, that is, injecting the mapper into the IOC container

BeanDefinition是对扫描到的bean的描述,每个类都有一个BeanDefinition对象一一对应,spring中所有的Bean实例化都是通过该对象生成的

在整合的过程中会出现多种BeanDefinition的包装类,都是用来生产spring Bean描述对象BeanDefinition的

1. When the @MapperScan annotation is loaded, it will trigger the @Import annotation

2. Trigger the registerBeanDefinitions method of the MapperScannerRegistrar class

3. The MapperScannerConfigurer object will be registered in the registerBeanDefinitions method, and the registration notification method postProcessBeanDefinitionRegistry will be triggered after the MapperScannerConfigurer registration is triggered

4. The ClassPathMapperScanner class is created in the postProcessBeanDefinitionRegistry method, which inherits from the spring scan class

5. The scan method of ClassPathMapperScanner will scan the mapper under the specified package, and at the same time inject the mapper's proxy object MapperFactoryBean

6. Finally, register the BeanDefinition object assembled by mapper to complete spring management

7. The following operations are the same as using mybatis alone, and the mapper is obtained through annotations

7. Supplement

  1. Here I explain it in the form of annotations, but there is actually an XML way
  2. Objects like SqlSessionFactory that we use in mybatis are configured through xml when integrating with spring, and are automatically assembled in the springboot project

Guess you like

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