Interviewer: how is Mybatis loaded and executed in SpringBoot?

Keywords: Java network server

This article mainly describes how mybatis is loaded and executed in springboot. Because there will be a lot of content involved, this time we will only explain the call relationship and key code points. In order to avoid the article being too long and sleepy to read, some details that do not affect the overall process will not be involved.

Source location https://github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-mybatis

1. Preparatory knowledge

  • FactoryBean what is a FactoryBean? Let's first look at the source code of FactoryBean
//Interfaces implemented by objects used in BeanFactory, which are themselves factories for individual objects. If a bean implements this interface, it is used as a factory for object exposure, rather than directly as a bean instance that will expose itself.
//Note: beans that implement this interface cannot be used as ordinary beans. A FactoryBean is defined in a bean style, but the object exposed for the bean reference (getObject()) is always the object it creates.
//FactoryBeans can support singletons and prototypes, and can create objects lazily or eagerly as needed at startup. The SmartFactoryBean interface allows more granular behavior metadata to be exposed.
//This interface is widely used in the framework itself, such as AOP org.springframework.aop.framework.ProxyFactoryBean or org.springframework.jndi.JndiObjectFactoryBean. It can also be used to customize components; However, this applies only to infrastructure code.
//FactoryBean is a procedural contract. Implementations should not rely on annotation driven injection or other reflection facilities. Getobjecttype () and GetObject () calls may arrive early in the boot process, even before any post processor settings. If you need access to other beans, implement BeanFactoryAware and get them programmatically.
//The container is only responsible for managing the lifecycle of FactoryBean instances, not the objects created by factorybeans. Therefore, the destroy method on exposed bean objects (such as java.io.Closeable.close() will not be called automatically. Instead, FactoryBean should implement DisposableBean and delegate any such close calls to the underlying object.
//Finally, the FactoryBean object participates in the synchronization of bean creation containing BeanFactory. Internal synchronization is usually not required except for delayed initialization within the FactoryBean itself (or similar).
package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

public interface FactoryBean<T> {

	String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    //Returns the bean object in the real beanfactory
	@Nullable
	T getObject() throws Exception;
    
	//Returns the type of the bean object in the real beanfactory
	@Nullable
	Class<?> getObjectType();
    
	//Single case or not
	default boolean isSingleton() {
		return true;
	}

}

The above is the source code of FactoryBean, and I deleted all the comments in the source code. The Chinese comments on the class are on the translated source code, and the comments on the method are added by myself. In short, the class of the time interface is a factory exposed as an object, and the actual bean object can be obtained only by calling getObject().

2. Spring boot integrates mybatis

  • The previous article simply said that when springboot starts, it will read the META-INF\spring.factories file and load the string of key=org.springframework.boot.autoconfigure.EnableAutoConfiguration as the class name (the startup will filter out those that do not conform to the current scene together with the content in META-INF\spring-autoconfigure-metadata.properties) The spring boot integration mybatis is also implemented in this way.

  • Who is responsible for the above file? Our main method will have @ SpringBootApplication annotation

There will be an @ EnableAutoConfiguration annotation on the SpringBootApplication

There will be an import annotation on this. The parameter is AutoConfigurationImportSelector.class. The class that really reads the above file is AutoConfigurationImportSelector.

AutoConfigurationImportSelector.java

//The real read code is here
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		//Read the value of key=org.springframework.boot.autoconfigure.EnableAutoConfiguration in META-INF\spring.factories file here
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
        //Here, read the value of key=org.springframework.boot.autoconfigure.AutoConfigurationImportFilter in META-INF\spring.factories file, and filter according to META-INF\spring-autoconfigure-metadata.properties
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

Reading the META-INF\spring-autoconfigure-metadata.properties file is in the constructor of the internal class ConfigurationClassFilter of AutoConfigurationImportSelector, and the real filtering is also in this internal class

		ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
		//META-INF\spring-autoconfigure-metadata.properties read here
			this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
			this.filters = filters;
		}
//This is also the method of ConfigurationClassFilter
List<String> filter(List<String> configurations) {
   long startTime = System.nanoTime();
   String[] candidates = StringUtils.toStringArray(configurations);
   boolean skipped = false;
   for (AutoConfigurationImportFilter filter : this.filters) {
   	//Perform filtering
      boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
         if (!match[i]) {
            candidates[i] = null;
            skipped = true;
         }
      }
   }
     + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
   }
   return result;
}

The default filter is 3, which is here

During reading, the META-INF\spring.factories configuration in mybatis-spring-boot-autoconfigure-2.2.0.jar (the first figure in this article) will be read and the following two classes will be loaded

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

Similarly, the META-INF\spring-autoconfigure-metadata.properties file in mybatis-spring-boot-autoconfigure-2.2.0.jar will be used for filtering.

The filter here actually uses the class name +. + Conditional * as the filter

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnClass=org.apache.ibatis.session.SqlSessionFactory,org.mybatis.spring.SqlSessionFactoryBean
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnSingleCandidate=javax.sql.DataSource

For example, in the above two lines, org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnClass determines whether it is filtered according to the existence of the class after the equal sign. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnSingleCandidate also determines whether the corresponding class exists according to the code. Multiple conditions are the relationship between and

The specific judgment code of these two conditions is located in OnBeanCondition

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
      AutoConfigurationMetadata autoConfigurationMetadata) {
   ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
   for (int i = 0; i < outcomes.length; i++) {
      String autoConfigurationClass = autoConfigurationClasses[i];
      if (autoConfigurationClass != null) {
          //Gets the value after the *. ConditionalOnClass equal sign
         Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
          //Make a judgment. If NULL is returned, it is OK, and if the condition does not exist, it is null
         outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
         if (outcomes[i] == null) {
             //Gets the value after the *. ConditionalOnSingleCandidate equal sign
            Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
                  "ConditionalOnSingleCandidate");
              //Make a judgment. If NULL is returned, it is OK, and if the condition does not exist, it is null
            outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
         }
      }
   }
   return outcomes;
}

In the current scenario, these two classes are consistent and will not be filtered out. These two classes will be loaded.

3. MybatisAutoConfiguration loading, beanfactory loading @ Mapper class

Let's take a look at the loading process, mainly the MybatisAutoConfiguration class, so we only look at this class here

//Here I'll stick out the annotations on the class and briefly introduce them

//We are all familiar with this annotation. I won't say more
@org.springframework.context.annotation.Configuration
//This is also a conditional annotation. The processed class is in the same class as the processing in the above configuration file
//This is to judge whether the corresponding class exists
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
//This is different from the processing in the configuration file. This is to judge whether there is only one bean definition of type DataSource.class in beanfactory, or there are multiple but one main bean
@ConditionalOnSingleCandidate(DataSource.class)
//This is to make the injection configuration file
@EnableConfigurationProperties(MybatisProperties.class)
//This is sorted
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
    ......
}

The following figure shows ConditionalOnClass, where ConditionalOnSingleCandidate executes processing, or in the OnBeanCondition class

After the filtering judgment, after determining that the MybatisAutoConfiguration class needs to be loaded, the internal classes and methods will be scanned, and the qualified ones will also be loaded, mainly looking for the @ Configuration and @ Bean annotations. We will load the following contents in the current class in turn

Load this internal class. The ConditionalOnMissingBean condition is currently valid. All conditions will be ignored. I won't say much about it. At the same time, because there is an Import annotation on the class, the class autoconfiguratedmappercannerregister.class will continue to be loaded,

Method @ Bean annotation on class

These two classes will also be loaded

MybatisAutoConfiguration, MapperScannerRegistrarNotFoundConfiguration, autoconfiguratedmapperscannerregistrar, SqlSessionTemplate and SqlSessionFactory will be loaded here as bean definitions (the latter two are methods).

Because autoconfiguratedmappercannerregister implements the ImportBeanDefinitionRegistrar interface, registerBeanDefinitions will be called to register additional bean definitions during loading.

This method is more important. Let's go in and have a look

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	//This is to judge whether there is a bean of AutoConfigurationPackages in beanFactory. It exists here
      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");
	  //Here is the name of the package to be scanned. Here will be {"com.example.springbootmybatis"}. In fact, that is where we find mapper. Let's talk about this separately later
      List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
      if (logger.isDebugEnabled()) {
        packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
      }
	 //The following code is mainly to define a bean and add it to BeanFactory
      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
      builder.addPropertyValue("processPropertyPlaceHolders", true);
      //This is the annotation type to scan, which is @ Mapper
      builder.addPropertyValue("annotationClass", Mapper.class);
      //Here is the path of the package to be scanned
      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());
    }

Here, let's take a look at the package name obtained by AutoConfigurationPackages.get(this.beanFactory)

First look at the inner call of this method

	public static List<String> get(BeanFactory beanFactory) {
		try {
            //BEAN = AutoConfigurationPackages.class.getName(), that is, get the bean whose name is AutoConfigurationPackages.class.getName(),AutoConfigurationPackages.BasePackages.class, and then call the get method of AutoConfigurationPackages.BasePackages
            //Let's analyze how this value comes from
			return beanFactory.getBean(BEAN, BasePackages.class).get();
		}
		catch (NoSuchBeanDefinitionException ex) {
			throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
		}
	}

Because our main method class has @ SpringBootApplication annotation, @ EnableAutoConfiguration annotation, @ AutoConfigurationPackage annotation and @ import annotation (AutoConfigurationPackages.Registrar. Class), when loading our main class SpringbootMybatisApplication, The registerBeanDefinitions method of AutoConfigurationPackages.Registrar will be called

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
        //The metadata here is our spring boot mybatisapplication
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
           	//Let's first look at the new PackageImports(metadata) method
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}
	......

	}
		//This is relatively simple, that is, to get some package names
		PackageImports(AnnotationMetadata metadata) {
            
			AnnotationAttributes attributes = AnnotationAttributes
					.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
            //Here is to get the "basePackages" attribute annotated on our main class SpringbootMybatisApplication. Since we have no configuration, this is null
			List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
              //Here is to get the "basepackageclass" attribute annotated on our main class SpringbootMybatisApplication. Since we have not configured it, we will not go to this for loop
			for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
				packageNames.add(basePackageClass.getPackage().getName());
			}
            //The packageNames here are empty and will go to the if branch
			if (packageNames.isEmpty()) {
                //packageNames add the package name com.example.SpringbootMybatisApplication where the current SpringbootMybatisApplication class is located
				packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
			}
            //In this.packageNames, there will only be com.example.springbootmybatis
			this.packageNames = Collections.unmodifiableList(packageNames);
		}

Let's look back at the registerBeanDefinitions above

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
       	//The above new PackageImports(metadata) has been analyzed. At this time, new PackageImports(metadata).getPackageNames().toArray(new String[0]) is {"com. Example. Springbootmybeatis"}
        //This method does not point in. Let's talk about it briefly here
        //The method is to add a bean definition (BasePackagesBeanDefinition, whose parameter is {"com.example.springbootmybatis"}) to the registry (that is, beanfactory). Therefore, the result returned by the above sentence of AutoConfigurationPackages.get(this.beanFactory) is {"com.example.springbootmybatis"}
		register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
	}
......

}

Let's continue to look at the bean definition registry. Registerbeandefinition (mappercannerconfigurer. Class. Getname(), builder. Getbeandefinition()) added in registry (that is, beanfactory) in public void registerbeandefinitions (annotation metadata, importing classmetadata, beandefinition Registry)

Since mappercannerconfigurer implements BeanDefinitionRegistryPostProcessor, it will be loaded before generating beans and call its postProcessBeanDefinitionRegistry method

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      //This is mainly to set some properties, such as the package name above, the annotation class name to be scanned, and so on
      processPropertyPlaceHolders();
    }
	//This class depends on its name. Everyone knows what it does. It mainly scans the mapper annotation class
    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);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    //Here is the annotation class to be scanned, and @ Mapper will be set here
    scanner.registerFilters();
    //Here, we need to scan according to the incoming package name. this.basePackage here is the com.example.springbootmybatis mentioned above
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

	public int scan(String... basePackages) {
        ......
		//Scan mapper here
		doScan(basePackages);
		......
	}

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //First, we'll enter here. Let's go in and have a look  
  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 {
     //We will come here. This method is also more important. Let's go in and have a look
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        //Traversal based on the passed in package name
		for (String basePackage : basePackages) {
            //Here is the mapper annotation class under the scan class path.
            //For example, the package name I passed in here is com.example.springbootmybatis, which will be converted into classpath*:com/example/springbootmybatis/**/*.class. This path will be parsed and searched, and the found class will be returned as the definition of BeanDefinition
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
                //Gets the name of the bean
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                //Here, the type of candidate is ScannedGenericBeanDefinition, so it will enter the if branch. It's nothing, just set some bean initialization related properties. Don't pay attention
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
                //Will also enter this if branch, and I won't go in to see this
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
                //This is to judge whether beanFactory contains the bean definition of beanName. If it does not, it will enter the branch. This branch is nothing special, that is, add the bean definition to beanFactory
				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;
	}

Continue to enter this method. This method is relatively long, but it is more important. Let's see with me. I'll omit all non key codes

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
	......
	//Here is to add a constructor parameter to the bean definition, that is, the class name of the mapper annotation class we scanned. Here is com.example.springbootmybeatis.mapper.usermapper. This is for the subsequent selection of which construction method to serve
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    //This is the class that sets the class of the corresponding bean. Here, it is set to org.mybatis.spring.mapper.MapperFactoryBean. Note that this class implements the FactoryBean interface
    definition.setBeanClass(this.mapperFactoryBeanClass);
	......

    if (!explicitFactoryUsed) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
       //This sentence is also more important and represents the attribute injection mode
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
	......
  }
}

Here, the loading of @ Mapper class is completed, and the following is completed when generating the corresponding bean

4. Beanfactory generates bean objects corresponding to @ Mapper class

Creating a bean object instance will call the doGetBean method of AbstractBeanFactory

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
				......
				// Create bean instance.
                //Since our is a singleton object, we will go to this branch
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
                            //In this method, a bean object will be created. Let's take a look at this method
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
                    //Because our sharedInstance object is, we will eventually call the doGetObjectFromFactoryBean method of FactoryBeanRegistrySupport to return the real userMapper bean object, that is, call the getObject() method of MapperFactoryBean
					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
	}

When you create a bean object instance, you will eventually go to the doCreateBean method of the AbstractAutowireCapableBeanFactory class

	//Let's analyze this code. I'll omit all irrelevant codes

		protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
            //From the name of this.factoryBeanInstanceCache, we can know that it is the cache of factoryBean instances. In fact, the instances created by our current userMapper have been cached here, but it doesn't matter. Even if the cache has not been created here before, the following 12 lines will be created. So we think we haven't created it before. Let's take a look at the 13 lines of code to create the factoryBean instance of userMapper
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
            //The factoryBean object of userMapper cannot be created here normally. It was created before this, but the creation method is also called
            //The purpose of this method is to find the corresponding class according to getBeanClass() of RootBeanDefinition, then find all construction methods, select the appropriate construction method according to the parameters of RootBeanDefinition.getConstructorArgumentValues() construction method, create the class object, and return the object wrapped by BeanWrapperImpl
            //In the processBeanDefinitions method above, the beanClass and constructorArgumentValues of RootBeanDefinition have been specially set.
            //Therefore, we actually call the maperfactorybean (class < T > mapperInterface) construction method here. The parameter mapperInterface is our mapper class com.example.springbootmybeatis.mapper.usermapper
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
        //This is to get the MapperFactoryBean object created
		Object bean = instanceWrapper.getWrappedInstance();
        //This is the type of object created, that is, org.mybatis.spring.mapper.MapperFactoryBean
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}
		......

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
            //Here is the attribute filling. Let's go here and have a look
			populateBean(beanName, mbd, instanceWrapper);
            //Since our MapperFactoryBean inherits SqlSessionDaoSupport, it inherits DaoSupport, and implements the InitializingBean interface, we will also call the afterpropertieset method of DaoSupport here
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
		return exposedObject;
	}
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	   ......
           //This is to obtain the RootBeanDefinition attribute injection mode. Our is set in the processBeanDefinitions method above
           //definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); That's it
    	int resolvedAutowireMode = mbd.getResolvedAutowireMode();
		if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
			......
			if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
                //Finally, we will go here, traverse all the properties of the class, filter them using the unsatisfiedNonSimpleProperties method, and inject the properties. Here, we will inject sqlSessionFactory and sqlSessionTemplate attributes (these two injected attributes are bean objects defined by methods in the MybatisAutoConfiguration class, as mentioned above)
				autowireByType(beanName, mbd, bw, newPvs);
			}
			pvs = newPvs;
		}
		......
	}

Afterpropertieset method of DaoSupport

	
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// Let abstract subclasses check their configuration.
        //This method is implemented by the subclass MapperFactoryBean. Let's go in and have a look
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

checkDaoConfig method of MapperFactoryBean

  protected void checkDaoConfig() {
	......
	//This is the Configuration of the SqlSessionTemplate injected before
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
         //We will add our mapper class here. this.mapperInterface here is com.example.springbootmybeatis.mapper.usermapper. Let's go here and have a look
        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();
      }
    }
  }

addMapper method of Configuration

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

addMapper method of MapperRegistry

  public <T> void addMapper(Class<T> type) {
		......
      try {
        //Add a MapperProxyFactory object with key = com.example.springbootmybeatis.mapper.usermapper in knownMappers
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        //Here is to find the mapper.xml file. Similarly, if we do not use XML configured sql, but use annotation, the specific search is realized through the following parse method. Let's go to the parse method
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

parse method of MapperAnnotationBuilder

  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      //This line is to load the mapper's XML file from the classpath. The specific path rules are as follows: type.getName().replace('.', '/') + ".xml". Therefore, if our mapper XML file is specified according to this rule, there is no need to specify the path of mapper.xml separately through mybatis.mapper-locations
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      //The following is to scan the annotation on the method to generate sql configuration. I won't go in here
      for (Method method : type.getMethods()) {
        if (!canHaveStatement(method)) {
          continue;
        }
        if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
            && method.getAnnotation(ResultMap.class) == null) {
          parseResultMap(method);
        }
        try {
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

Let's go back to the doGetBean method and look at the following

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
					......
                    //Because our sharedInstance object is, we will eventually call the doGetObjectFromFactoryBean method of FactoryBeanRegistrySupport to return the real userMapper bean object, that is, call the getObject() method of MapperFactoryBean
					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
	}

getObject method of MapperFactoryBean

  public T getObject() throws Exception {
  //This getSqlSession() is the property injection above. org.mybatis.spring.SqlSessionTemplate,
    return getSqlSession().getMapper(this.mapperInterface);
  }

Finally, the getMapper method of MapperRegistry will be called

  
	public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // We mentioned in the addMapper method above that knownMappers has added the MapperProxyFactory object of key = com.example.springbootmybeatis.mapper.usermapper,
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //Here is the method to call the newInstance of MapperProxyFactory
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

newInstance method of MapperProxyFactory

public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
  //Finally, call here to create a MapperProxy proxy proxy object, which is the real created bean object
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

Here, springboot integrates mybatis to create the mapper object, and the whole process is here. The method of calling mapper later is actually implemented through MapperProxy proxy proxy. The implementation process of calling mybatis in specific springboot will be explained to you in the next article.

5. About @ MapperScan

You can see that the @ MapperScan annotation is not used in my demo. When to use this annotation? Let's take a look at the source code

There will be two annotations @ import (mappercannerregister. Class) and @ repeatable (mappercans. Class) on the mappercan annotation. The mappercannerregister annotation implements the importbeandefinitionregister. The registerBeanDefinitions method will be called during the loading of the main class,

  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //This is to obtain the related attributes of MapperScan annotation on the main class. For example, our configuration is (@ MapperScan(basePackages = "com.example.springbootmybatis")), such as basePackages attribute, etc. are all on this annotation
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      //Based on these properties, a bean definition of mappercannerconfigurer class will be created and added to beanfactory
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

Let's take a look at the internal class mybatisautoconfiguration. Mappercannerregistrarnotfoundconfiguration mentioned earlier

  @org.springframework.context.annotation.Configuration
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  //Since we have added the bean definition of mappercannerconfigurer in beanFactory above, this condition will not hold, and the autoconfiguratedmappercannerregister class imported in the import annotation above will not be executed
  @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.");
    }

  }

Therefore, the difference between @ mappercan and @ mappercannerconfigurer mainly lies in the different generation locations defined by the bean mappercannerconfigurer

There are a lot of contents in the whole. If you feel that you don't speak clearly or clearly, you are welcome to leave a message in the comment area.

 

Posted by pandhandp on Fri, 03 Dec 2021 07:03:05 -0800