Spring integration Mybatis source code analysis: @ MapperScan principle

Tip: after the article is written, the directory can be generated automatically. Please refer to the help document on the right for how to generate it

preface

Last article Referring to the postProcessBeanDefinitionRegistry() method of mappercannerconfigurer, this article continues to delve into this method.

1, Mapper registration process

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    //Omit some codes
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		// Focus on doScan()
		doScan(basePackages);
		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  	// Call the doScan() method of the parent class, scan all mappers according to the path configured in @ MapperScan, and encapsulate them into BeanDefinitionHolder
    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 {
    // Processing the scanned bd is the most critical step
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }

Here we have obtained all mappers and encapsulated them into BeanDefinitionHolder. The next step is the most critical step in dealing with BeanDefinitionHolder. Let's see how to deal with it.

Example: pandas is a NumPy based tool created to solve data analysis tasks.

2, processBeanDefinitions() parsing process

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      // Omit some codes
      String beanClassName = definition.getBeanClassName();
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      definition.setBeanClass(this.mapperFactoryBeanClass);
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
   	  // Omit some codes
    }
  }

All mappers will be traversed in this method. There are two key steps:
1. Add the parameter value of a constructor: beanClassName, that is, the fully qualified class name of the current Mapper.
2. Set bd's BeanClass to MapperFactoryBean Class, there's a wave of cheating here. Why do you do this? Then you have to look at MapperFactoryBean.

3, MapperFactoryBean

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  private Class<T> mapperInterface;
  private boolean addToConfig = true;
  public MapperFactoryBean() {
    // intentionally empty
  }
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  @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 {
        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();
      }
    }
  }

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

1.FactoryBean

MapperFactoryBean implements FactoryBean. As we all know, when spring's getBean() method is called, if the current object is of FactoryBean type, spring will call its getObject() method. You can see that the getObject() of MapperFactoryBean is actually the processing logic of Mybatis;

2.InitializingBean

MapperFactoryBean also inherits SqlSessionDaoSupport. See the specific inheritance relationship below:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
public abstract class SqlSessionDaoSupport extends DaoSupport
public abstract class DaoSupport implements InitializingBean

That is to say, MapperFactoryBean is of InitializingBean type. When spring initializes an object, if the current object is of InitializingBean type, spring will call its afterpropertieset () method.

public abstract class DaoSupport implements InitializingBean {
	/** Logger available to subclasses. */
	protected final Log logger = LogFactory.getLog(getClass());
	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// Here is the template mode. The specific processing logic is placed in the subclass implementation
		checkDaoConfig();
		// Omit some codes
	}
	// Omit some codes
}

Let's look at the implementation of MapperFactoryBean:

 @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 {
        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();
      }
    }
  }

You can see that MapperFactoryBean will first try to get Mapper here (of course, it can't get it). If it can't get it, it will call configuration.addMapper(this.mapperInterface), which is the processing logic of Mybatis.

3.this.mapperInterface

this. Where did mapperinterface come from? The db processing mentioned earlier is useful:

definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);

Take a look at the MapperFactoryBean constructor:

 public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

See everything here clearly.

summary

Here, the parsing of @ MapperScan is completed. In fact, the process is not complicated. First, follow the scan package path we configured to obtain all mappers and package them into the BeanDefinitionHolder set, then traverse the whole set, set the Mapper type to MapperFactoryBean, and use MapperFactoryBean to complete the operation of Mybatis.
The above is only my own understanding. If there is anything wrong, please correct it and learn and make progress together.

Keywords: Java Spring Back-end

Added by nitromaster on Thu, 16 Dec 2021 19:02:15 +0200