This is the 10th day of my participation in the August More Text Challenge. For details, see:August is more challenging

SqlSessionFactory = SqlSessionFactory = SqlSessionFactory = SqlSessionFactory = SqlSessionFactory = SqlSessionFactory; But it’s not clear why Mapper works when we just use @autowired.

First of all, the debug can be found at this time of mapper object is org. Apache. Ibatis. Binding. MapperProxy object, obviously somewhere will these mapper interface agent for MapperProxy injection in the Spring container.

Tb1Mapper is only an interface. It is not possible to create an object. This object should be created somewhere by dynamic proxy and injected into the Spring container.

The only thing in our code that relates to these Mapper interfaces is the @mapperscan annotation.

@MapperScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// This is the main one
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  String[] value() default {};
  String[] basePackages() default{}; Class<? >[] basePackageClasses()default {};
  / /...
}
Copy the code

Can see the annotations @ Import (MapperScannerRegistrar. Class) this class, @ Import role is to Import the configuration class, so take a look at this first class. (Of course, if you do not use this annotation, you will create the MapperScannerRegistrar bean in autoconfiguration as well)

MapperScannerRegistrar

The code is simplified as follows:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware {

  @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)); }}void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    // addPropertyValue is a series of addPropertyValue operations, which basically set the @mapperscan configuration to the BeanDefinition
    builder.addPropertyValue("processPropertyPlaceHolders".true);
    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    // Register the BeanDefinition of the MapperScannerConfigurer with Spring.registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }}Copy the code

MapperScannerRegistrar class – this class implements the ImportBeanDefinitionRegistrar this interface, this interface is spring provides interface, the purpose is to realize the dynamic injection of bean, It’s just that we have to build our Own BeanDefinition and register it.

The main method is to registerBeanDefinitions. The main function of this method is to set the @mapperscan annotation to the BeanDefinition of the MapperScannerConfigurer class. This is essentially creating the MapperScannerConfigurer bean.

MapperScannerConfigurer

After the above steps, the MapperScannerConfigurer bean is designed to obtain the path information of the Mapper interface configured in @mapPERScan. A quick look at the core implementation of this class:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor.InitializingBean.ApplicationContextAware.BeanNameAware {
  private String basePackage;
  / /... Some of the properties
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    / /... Some set operations, but mostly null at this point, mainly the scan method.
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }}Copy the code

MapperScannerConfigurer BeanDefinitionRegistryPostProcessor is achieved, the interface is spring BeanFactoryPostProcessor interface, Provides a postProcessBeanDefinitionRegistry, further the method to register BeanDefinition in Spring.

Special note:

  • BeanFactoryPostProcessorthisPostProcessorIs executed when the bean Definition information has been loaded, but not initializedpostProcessBeanFactoryMethods. This interface is used to modify the property information of a BeanDefinition.
  • BeanDefinitionRegistryPostProcessorIs in theBeanFactoryPostProcessorBefore, he’s on the BeanDefinition is going to be loaded, and we can see that the parameter of his interface method isBeanDefinitionRegistrySo you can register some new BeanDefinitions and other components in Spring.

This method mainly does two things please:

  1. The ClassPathMapperScanner object is createdscannerAnd pass in the Registry object.
  2. callscannerThis is the core method of scanning mapper.

So let’s look at ClassPathMapperScanner again.

ClassPathMapperScanner

The code is simplified as follows:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  private SqlSessionFactory sqlSessionFactory;
  private SqlSessionTemplate sqlSessionTemplate;
  
  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
  
  / / constructor, registry to continue the incoming ClassPathBeanDefinitionScanner
  public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
    super(registry, false);
  }
  // Scan all interfaces under the MapperScan configuration package and create BeanDefinition to register with Spring.
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // Call the scan method of the superclass
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    processBeanDefinitions(beanDefinitions);
    return beanDefinitions;
  }

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    // Iterate over mapper interfaces that have been temporarily encapsulated as BeanDefinitions
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      // Set the class of BeanDefinition to MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass); }}Copy the code

ClassPathMapperScanner inherited the package of the Spring scanner ClassPathBeanDefinitionScanner fu wrote doScan method at the same time, The scanner. The scan is the actual call ClassPathBeanDefinitionScanner scan method.

Let’s look at what this method does:

  1. Call the parent classClassPathBeanDefinitionScannerthedoScanThe interface class of mapper interface package is scanned and preliminarily encapsulated asBeanDefinitionHolder(You can just call itBeanDefinition)
  2. These BeanDefinitionsuper.doScan(basePackages);When the call is complete, it is all registeredregister.
  3. Now, for these beandefinitions that we get, callprocessBeanDefinitionsStep by step processing,I’ve only kept the core here: set the class of the BeanDefinition to MapperFactoryBean!

Finally, we get the BeanDefinition of mapper interface as shown in the figure below:

This step sets the class of the BeanDefinition of the Mapper interface to MapperFactoryBean, so Spring will call the constructor of MapperFactoryBean when it initializes the bean.

Let’s move on to the MapperFactoryBean class:

MapperFactoryBean

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  
  private Class<T> mapperInterface;
  // This property is extracted from SqlSessionDaoSupport, which is an abstract class
  private SqlSessionTemplate sqlSessionTemplate;

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

  @Override
  public Class<T> getObjectType(a) {
    return this.mapperInterface;
  }
    
  public MapperFactoryBean(a) {
    // intentionally empty
  }

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

You can see that MapperFactoryBean is a FactoryBean, so the Bean he ends up injecting into the container is the method returned by getObject, which is:

getSqlSession().getMapper(this.mapperInterface);

The tracking of this method is as follows:

public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this); } ⬇ ️ # Configurationpublic <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  returnmapperRegistry.getMapper(type, sqlSession); } ⬇ ️ # MapperRegistrypublic <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}
⬇️ # MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  returnnewInstance(mapperProxy); } ⬇ ️ #MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
  // You can see that it is based on the JDK's dynamic proxy that creates the proxy Bean
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
Copy the code

So, the objects that we inject @Autowired are actually objects of this class called MapperProxy, so let’s look at this class.

MapperProxy

public class MapperProxy<T> implements InvocationHandler.Serializable {
  
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // If it is an Object method, it indicates that it is not a method that calls SQL.
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // Mapper SQL call method
        returncachedInvoker(method).invoke(proxy, method, args, sqlSession); }}catch (Throwable t) {
      throwExceptionUtil.unwrapThrowable(t); }}}Copy the code

This part of the specific is how to call the execution of SQL, this part can see me between Mybatis source parsing – quick glance > use process.