Mybatis源码分析(一)MapperProxy 的初始化
2020/6/12 17:26:50
本文主要是介绍Mybatis源码分析(一)MapperProxy 的初始化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一、前言
我们用Spring整合mybatis的时候一定见过这两个注解
@Mapper
使用在mapper接口上,将接口托管给Spring管理。@MapperScan
用来开启包扫描,扫描项目某路径下的Mapper接口。
1.1 @MapperScan
@MapperScan
无疑更方便,让我们来看下它做了什么事情?
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { 复制代码
/** * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using * an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration. * * @author Michael Lanyon * @author Eduardo Macarron * @author Putthiphong Boonphong * * @see MapperFactoryBean * @see ClassPathMapperScanner * @since 1.2.0 */ 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) { //获取@MapperScan注解上的属性 AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } 复制代码
MapperScan@Import
了一个类MapperScannerRegistrar.class
,它实现了ImportBeanDefinitionRegistrar
接口,也就是说MapperScannerRegistrar拥有了向Spring注册Bean的能力。
Spring在执行ConfigurationClassParser
的doProcessConfigurationClass()
方法的时候会用getImports()
扫描@Import
的类。在processImports()
中MapperScannerRegistrar会被放到importBeanDefinitionRegistrars
列表中。后面就可以被ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsForConfigurationClass()
方法加载到了。
// Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), true); 复制代码
…… else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } 复制代码
来看下Spring加载MapperScannerRegistrar回调的registerBeanDefinitions
方法
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { 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); } …… builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } 复制代码
BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class)
构建了一个MapperScannerConfigurer.class
类型的BeanDefinition(Spring中的bean),此外给它设置了几个属性:processPropertyPlaceHolders
、annotationClass
和basePackage
,annotationClass
就是要扫描的Mapper的默认的注解类,basePackage
就是要扫描的包的根路径。
MapperScannerConfigurer.class
,Spring整合Mybatis的核心类 它的作用是扫描项目中的Dao类,将其创建为Mybatis的Mapper对象也就是MapperProxy
。这个我们一会儿会详细讲解。
1.2 @Mapper
SpringBoot整合Mybatis的时候,官方文档里提到Springboot可以自动扫描Mapper类
- Auto-scan your mappers, link them to the SqlSessionTemplate and register them to Spring context so they can be injected into your beans
我们对@Mapper
find usage 可以发现MybatisAutoConfiguration
这个类有使用到
进入MybatisAutoConfiguration可以看到MybatisAutoConfiguration
实现了InitializingBean
接口,那么就必然会执行afterPropertiesSet()
方法,我们在看下具体的实现
@org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnSingleCandidate(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) public class MybatisAutoConfiguration implements InitializingBean { 复制代码
/** * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan * mappers based on the same component-scanning path as Spring Boot itself. */ @org.springframework.context.annotation.Configuration @Import(AutoConfiguredMapperScannerRegistrar.class) @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."); } } 复制代码
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
不用我多说了吧,我们在看下如果没有这两个类 Import的AutoConfiguredMapperScannerRegistrar.class
,和MapperScannerRegistrar.class
有异曲同工之妙。但是我还是建议大家使用@MapperScan
,省事!
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { …… BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); builder.addPropertyValue("annotationClass", Mapper.class); builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); Stream.of(beanWrapper.getPropertyDescriptors()) // Need to mybatis-spring 2.0.2+ .filter(x -> x.getName().equals("lazyInitialization")).findAny() .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}")); registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } 复制代码
二、MapperScannerConfigurer
从MapperScannerConfigurer的类图我们可以了解它的实现情况BeanNameAware
:bean在创建的时候,会将bean的名称通过setBeanName()
设置在bean中,这样外部程序就可以通过getBeanName()
获取到bean的名称。ApplicationContextAware
:任意实现ApplicationContextAware
的子类在被创建的时候,Spring会将ApplicationContext
对象自动注入到当前bean中。InitializingBean
:InitializingBean
接口为bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet
方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。BeanDefinitionRegistryPostProcessor
:对标准BeanFactoryPostProcessor
SPI的扩展,允许在常规BeanFactoryPostProcessor
检测开始之前注册更多的bean定义。通常在我们想自定义BeanDefinition
用到。MapperScannerConfigurer
主要实现了扫描特定包并创建Mapper对象,并交给Spring管理。BeanFactoryPostProcessor
:BeanFactory
的后置处理器,BeanFactoryPostProcessor
在spring容器加载了bean的定义文件之后,在bean实例化之前,可以在postProcessBeanFactory()
中根据需要对BeanDefinition
进行修改,例如可以把bean的scope从singleton改为prototype。
2.1 BeanDefinitionRegistryPostProcessor
MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
接口,因此Spring容器会在invokeBeanDefinitionRegistryPostProcessors
()回调postProcessBeanDefinitionRegistry()
方法。
@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); scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } scanner.registerFilters(); scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } 复制代码
- 这里设置
sqlSessionFactory
和sqlSessionTemplateBeanName
,后续生成的Mapper自然会受到他们的管理 - 调用了ClassPathMapperScanner的
scan()
方法
2.2 ClassPathMapperScanner
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); 复制代码
@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; } 复制代码
- super.doScan()调用父类的doScan方法,解析配置文件并构建对应的BeanDefinitionHolder对象。
- processBeanDefinitions() 增加BeanDefinitions定义,加入Mybatis特性
2.2.1 doScan
/** * Perform a scan within the specified base packages, * returning the registered bean definitions. * <p>This method does <i>not</i> register an annotation config processor * but rather leaves this up to the caller. * @param basePackages the packages to check for annotated classes * @return set of beans registered if any for tooling registration purposes (never {@code null}) */ protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 扫描候选组件的类路径 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); //生成bean名称 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //注册bean定义 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; } 复制代码
在指定的基础包中执行扫描,返回已注册的bean定义。MapperScanner执行之前,doscan在processConfigBeanDefinitions()也会被执行到用来做Config的扫描。
可以参考spring 扫描BeanDefinition详解2.2.2 processBeanDefinitions
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 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 // 这里就设置了生成的Mappe的类是mapperFactoryBeanClass = MapperFactoryBean.class; definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { //将SqlSessionFactory放入MapperFactoryBean属性中,在实例化时可以自动获取到该SqlSessionFactory。 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."); } //如果sqlSessionTemplate不为空,则放入到属性中,以便Spring在实例化MapperFactoryBean时可以得到对应的SqlSessionTemplate。 definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); } } 复制代码
processBeanDefinitions()
方法设置了BeanDefinition类为MapperFactoryBean
,在Spring中我们可以通过FactoryBean对象的getObject()
方法获得构建的实例。另外在MapperFactoryBean属性中还设置了SqlSessionFactory
和SqlSessionTemplate
(sqlSessionTemplate不为空)。
注意: 这里只是修改了BeanDefinition,Mapper还并未初始化。
三、 MapperFactoryBean
3.1 DaoSupport
/** * Generic base class for DAOs, defining template methods for DAO initialization. * * <p>Extended by Spring's specific DAO support classes, such as: * JdbcDaoSupport, JdoDaoSupport, etc. * * @author Juergen Hoeller * @since 1.2.2 * @see org.springframework.jdbc.core.support.JdbcDaoSupport */ 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 { // Let abstract subclasses check their configuration. checkDaoConfig(); // Let concrete implementations initialize themselves. try { initDao(); } catch (Exception ex) { throw new BeanInitializationException("Initialization of DAO failed", ex); } } 复制代码
通过文档注释可以知道,DaoSupport定义了初始化的模版方法
- checkDaoConfig()检查或构建dao的配置信息
/** * Abstract subclasses must override this to check their configuration. * <p>Implementors should be marked as {@code final} if concrete subclasses * are not supposed to override this template method themselves. * @throws IllegalArgumentException in case of illegal configuration */ 复制代码
- initDao()初始化Dao相关的方法
/** * Concrete subclasses can override this for custom initialization behavior. * Gets called after population of this instance's bean properties. * @throws Exception if DAO initialization fails * (will be rethrown as a BeanInitializationException) * @see org.springframework.beans.factory.BeanInitializationException */ 复制代码
3.2 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(); } } } 复制代码
mapperInterface
就只刚才扫描到的mapper接口- 如果已经没有注册过,就调用
configuration.addMapper()
把它加在configuration
中
protected final MapperRegistry mapperRegistry = new MapperRegistry(this); …… public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } 复制代码
我们来看下核心类MapperRegistry
3.3 MapperRegistry
public class MapperRegistry { private final Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); 复制代码
- 这里的
Configuration
是Mybatis的全局配置对象。包含各种xml或者注解解析的配置。 knownMappers
放的是Mapper
和MapperProxyFactory
表示Mapper是否已经被添加。
MapperRegistry中两个重要的方法addMapper
和getMapper
。addMapper为*Mapper创建对应的MapperProxyFactory
映射关系,getMapper顾名思义就是获取MapperProxyFactory
对象
3.3.1 addMapper
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { 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. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } 复制代码
- 已注册的Mapper会抛出异常
- 为
Mapper
创建MapperProxyFactory
对象并放在map里 - 如果Mapper对应的xml文件未加载,触发xml的绑定操作,将xml中的sql语句与Mapper建立关系。在后边会详细介绍。
3.3.2 getMapper
public <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); } } 复制代码
- 根据类型从
knownMappers
获取相对应的MapperProxyFactory
对象 - newInstance(sqlSession)创建MapperProxy对象
观察
getMapper
的堆栈调用情况不难发现,在扫描Mapper注入的时候回去调用MapperFactoryBean
的getObject()
方法生成Mapper的代理对象。MapperProxy
的创建到这里就结束了。MapperProxy与XMl文件或注解中的sql语句建立关联,我们后面继续讨论。
这篇关于Mybatis源码分析(一)MapperProxy 的初始化的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-31全网首发第二弹!软考2024年5月《软件设计师》真题+解析+答案!(11-20题)
- 2024-05-31全网首发!软考2024年5月《软件设计师》真题+解析+答案!(21-30题)
- 2024-05-30【Java】百万数据excel导出功能如何实现
- 2024-05-30我们小公司,哪像华为一样,用得上IPD(集成产品开发)?
- 2024-05-30java excel上传--poi
- 2024-05-30安装笔记本应用商店的pycharm,再安排pandas等模块,说是没有打包工具?
- 2024-05-29java11新特性
- 2024-05-29哪些无用敏捷指标正在破坏敏捷转型?
- 2024-05-29鸿蒙原生应用再新丁!新华社 入局鸿蒙
- 2024-05-29设计模式 之 迭代器模式(Iterator)