本文源代码来源于mybatis-spring-boot-starter的2.1.2版本
一、前言
我们用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类图从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的扫描。
image可以参考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
MapperFactoryBean3.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语句建立关联,我们后面继续讨论。
image
网友评论