Analyzing how Mybatis integrates into the framework using Spring's extension points, Mybatis's own extension points are no longer covered in this analysis
Build environment
Upload Github https://github.com/mybatis/spring .After several unsuccessful attempts through Git, the zip package was directly downloaded and imported into Idea.
The import process will be a bit slow, and a lot of things will be downloaded.Be sure to modify Maven's configuration file and local warehouse address, otherwise packages you've previously downloaded will be downloaded to the local warehouse on drive C
Test Code
Create a new directory directly under the source directory to write test code
Test Class
@Configuration @MapperScan("com.jv.mapper") @ComponentScan("com.jv.scan") public class TestMybatis { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestMybatis.class); UserService bean = ac.getBean(UserService.class); System.out.println(bean.query()); } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); PooledDataSource dataSource = new PooledDataSource(); dataSource.setDriver("org.mariadb.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://192.168.10.12:3306/acct?useSSL=false&serverTimezone=UTC"); dataSource.setUsername("dev01"); dataSource.setPassword("12340101"); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } }
Service class
@Service public class UserService { @Autowired private UserMapper userMapper; public List<User> query(){ return userMapper.query(); } }
Mapper class
public interface UserMapper { @Select("SELECT name,age FROM user") List<User> query(); }
Entity Class
@ToString public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } private Integer age; }
Note: Always modify pom.xml before running.Because Spring-related dependencies imported by Mybatis do not take effect at runtime
<scope>provided</scope>comment out all, otherwise run better than class
Table structure:
CREATE TABLE `user` (
`name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`age` int(4) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
insert into user(name,age) values("Messi",35);
commit;
Source Code Analysis
The use of @MapperScan can be found in the official mybatis-spring documentation: http://mybatis.org/spring/mappers.html
Register BeanDefinition
Now that integration with Spring is done through @MapperScan, start with it
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { .... }
Where @Import(MapperScannerRegistrar.class) is the focus, look at MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { /** * {@inheritDoc} * * @deprecated Since 2.0.2, this method not used never. */ @Override @Deprecated public void setResourceLoader(ResourceLoader resourceLoader) { // NOP } /** * {@inheritDoc} */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { /** * Register a BeanDefinition for MapperScannerConfigurer, which implements BeanDefinitionRegistryPostProcessor * BeanDefinitionRegistryPostProcessor The implementation class of the interface, once placed in the Spring container, can act as a function of registering the BeanDefinition it needs when the Spring container starts */ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { builder.addPropertyValue("annotationClass", annotationClass); } Class<?> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { builder.addPropertyValue("markerInterface", markerInterface); } //Custom BeanNameGenerator Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass)); } //Customize MapperFactoryBean Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); } //Customize sqlSessionTemplate String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef"); if (StringUtils.hasText(sqlSessionTemplateRef)) { builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef")); } //Customize sqlSessionFactory String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef"); if (StringUtils.hasText(sqlSessionFactoryRef)) { builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef")); } //Generate base package paths for all scans to scan List<String> basePackages = new ArrayList<>(); basePackages.addAll( Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList())); basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText) .collect(Collectors.toList())); basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName) .collect(Collectors.toList())); //Set Delayed Loading String lazyInitialization = annoAttrs.getString("lazyInitialization"); if (StringUtils.hasText(lazyInitialization)) { builder.addPropertyValue("lazyInitialization", lazyInitialization); } builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); //Register BeanDefinition for MapperScannerConfigurer registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } }
MapperScannerRegistrar implements ImportBeanDefinitionRegistrar
This is the key point for its integration into Spring, which you can refer to for ImportBeanDefinitionRegistrar https://my.oschina.net/u/3049601/blog/3129295
MapperScannerRegistrar completes the external import of BeanDefinition corresponding to the MapperScannerConfigurer class when the Spring container is initialized
MapperScannerConfigurator implementations BeanDefinitionRegistryPostProcessor (another extension of Spring and also extends BeanDefinition registration functionality), but it takes effect later than ImportBeanDefinitionRegistrar because the former triggers when Spring's ConfigurationClassPostProcessor Implements PriorityOrder.This class is the key class for all Mappers in the scan path
/** * BeanDefinitionRegistryPostProcessor Recursively search for interfaces from the base package and register them as MapperFactoryBean s * MapperFactoryBean Importantly, it implements InitializingBean, Spring makes its abstract method afterPropertiesSet call after the Bean property is set to complete some initialization */ public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { .................Omit some code.................. @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); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); //The default is MapperFactoryBean, which can be customized scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } scanner.registerFilters(); //Start Scanning scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } .................Omit some code.................. }
ClassPathMapperScanner inherits from Spring.ClassPathBeanDefinitionScanner and overrides the doScan method
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { .................Omit some code.................. /** * Overrides the doScan method of ClassPathBeanDefinitionScanner, but the scanning is done by the doScan of the parent class */ @Override 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; } /** * Overrides the processBeanDefinitions method of ClassPathBeanDefinitionScanner * Complete property filling for BeanDefinition * setAutowireMode=AbstractBeanDefinition.AUTOWIRE_BY_TYPE is the key to Spring's automatic injection based on type. * @param beanDefinitions */ private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface"); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean /** * The custom Mapper interface is only the initial Class of the Bean, and when Spring initializes, the Bean's lass is actually a MapperFactoryBean */ definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 //Set BeanClass to MapperFactoryBean definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { LOGGER.warn( () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { LOGGER.warn( () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); // Quite important, this is Spring's dependency injection based on its type. // When @Autowired is used, Spring is not automatically injected by default, that is, autowireMode is AUTOWIRE_NO definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); } } .................Omit some code.................. }
The code above completes a class scan of the basePackage, and Mybatis enhances the BeanDefinition generated from the scan, with two important points:
1.BeanClass=MapperFactoryBean
That is, after the initial Spring container initialization is complete, all Mappers are not actually instantiated (you can see if there are any objects in the XXXApplicationContext.beanFactory.FactoryBeanObjectCache), but rather their FactoryBean has already been instantiated.Create it when a Mapper is needed, or if it is a singleton, place the instantiated Bean in the FactoryBeanObjectCache
2.autowireMode=AUTOWIRE_BY_TYPE
Auto-injection by type is not directly related to @Autowired. Spring defaults to AUTOWIRE_NO, but Spring finds that you use the @Autowired annotation to automatically inject by type.Automatic injection by type must have a setXXX method.
Represents scanned classes that support instantiation by type. Why set this value?Our own Mapper is an interface, so why inject murmur into it?The root cause is MapperFactoryBean extends SqlSessionDaoSupport
There are two set methods in SqlSessionDaoSupport:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
You can verify that you commented out "definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)" with an error:
Represents no injection success and verifies the importance of AUTOWIRE_BY_TYPE
As of this point, the BeanDefinition (BeanClass=MapperFactoryBean) of the Mybatis related class has been fully registered and instantiated.
instantiation
There is a very important point to instantiate:
As mentioned earlier, MapperFactoryBean is the one that completes instantiation, not the real Mapper. Why?
Next, look at the class diagram for MapperFactoryBean
You can see that the InitializingBean is finally implemented, and Spring calls the afterPropertiesSet() method of the implementation class after completing the property filling for the Bean that implements the interface.
Let's see what the afterPropertiesSet() method does
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { //The checkDaoConfig of MapperFactoryBean is called at runtime this.checkDaoConfig(); try { this.initDao(); } catch (Exception var2) { throw new BeanInitializationException("Initialization of DAO failed", var2); } }
MapperFactoryBean's checkDaoConfig method
protected void checkDaoConfig() { super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { //very important 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 configuration.addMapper(this.mapperInterface) completes the MappedStatement addition.
configuration is a property in DefaultSqlSesstionFactory (default), which is globally unique
MappedStatement describes a SQL to execute, parameters, return types, and so on
With this step, everything you need to call the true query method is ready.
Summary: Mybatis uses @Import (class implements ImportBeanDefinitionRegistrar), BeanDefinitionRegistryPostProcessor, and InitializingBean extensions to complete the integration.
The following classes of Mybatis are important:
MapperScan
MapperScannerRegistrar
MapperScannerConfigurer
ClassPathMapperScanner
MapperFactoryBean
DefaultSqlSessionFactory
Configuration
MapperStatement