流程简单说明
- 由
@MapperScan
引入MapperScannerRegistrar
注册一个MapperScannerConfigurer
的BeanDefinition
对象 MapperScannerConfigurer
会扫描指定包的Mapper接口类,为Mapper接口类构建MapperFactoryBean
的BeanDefinition
对象- 当spring 初始化
MapperFactoryBean
Bean对象时,会调用FactoryBean # getObject()
方法,该方法会生成对应Mapper接口的org.apache.ibatis.binding.MapperProxy
代理对象,spring会将该代理对象作为Bean对象存放到缓存中,所以我们才可以通过@Autowire
引入。
@MapperScan
在 @MapperScan
注解中,引入了一个 MapperScannerRegistrar
注册器
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
...
}
MapperScannerRegistrar
MapperScannerRegistrar
注册器 是一个 ImportBeanDefinitionRegistrar
实现类,用于注册 一个 MapperScannerConfigurer
的BeanDefinition
对象,并将@MapperScan
中的配置信息添加到该BeanDefinition
的PropertyValues
中
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);
//将@MapperScan中的配置信息添加到该BeanDefinition的PropertyValues中
...
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
它就相当于以前的xml配置:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<!-- optional unless there are multiple session factories defined -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
MapperScannerConfigurer
MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor 实现类,主要用于注册 MapperFactoryBean 的BeanDefinition对象.
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
...
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));
}
}
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
方法会新建一个 ClassPathMapperScanner
对象,通过 ClassPathMapperScanner
对象,除了将@MapperScan
中的配置信息配置进去以外,通过 ClassPathMapperScanner # scan(String... basePackages)
方法进行扫描指定包的Mapper 层接口类 并注册 MapperFactoryBean
的BeanDefinition
对象。
scan(String... basePackages)
是 ClassPathMapperScanner
的父类ClassPathBeanDefinitionScanner
方法
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
/**
* Perform a scan within the specified base packages.
* @param basePackages the packages to check for annotated classes
* @return number of beans registered
*/
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
/**
* 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());
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);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
}
ClassPathMapperScanner
重写了 ClassPathBeanDefinitionScanner # doScan(String ... basePackages)
方法,再通过父级方法扫描完指定包名得到 BeanDefinitionHolder
列表后,对这个 BeanDefinitionHolder
进行处理
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
@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;
}
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
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() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
}
在processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions)
中,遍历 beanDefinitions
的所有BeanDefinitionHolder
对象,取出 BeanDefinitionHolder
对象的 BeanDefinition
对象,修改 BeanDefinition
对象的 beanClass
属性为MapperFactoryBean.class
,并增加了构造方法参数对象beanClassName
,beanClassName
是BeanDefinition
对象中原来的 beanClass
的类名,原来的beanClass
其实 Mapper 层的接口类。
这里其实相当于以前xml配置:
<bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean id="oneMapper" parent="baseMapper">
<property name="mapperInterface" value="my.package.MyMapperInterface" />
</bean>
<bean id="anotherMapper" parent="baseMapper">
<property name="mapperInterface" value="my.package.MyAnotherMapperInterface" />
</bean>
MapperFactoryBean
MapperFactoryBean
是一个 FactoryBean
,它只有一个接收一个Class<?>
对象的构造方法。该Class<?>
对象对应 MapperFactoryBean
的 mapperInterface
属性,该属性就是 Mapper 层的Mapper接口类。
SqlSessionDaoSupport
MapperFactoryBean
继承了 SqlSessionDaoSupport
, spring 会对SqlSessionDaoSupport
自动装配SqlSessionFactory
,然后SqlSessionDaoSupport
会使用SqlSessionFactory
创建出SqlSessionTemplate
对象。
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
/**
* Set MyBatis SqlSessionFactory to be used by this DAO. Will automatically create SqlSessionTemplate for the given
* SqlSessionFactory.
*
* @param sqlSessionFactory
* a factory of SqlSession
*/
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
/**
* Create a SqlSessionTemplate for the given SqlSessionFactory. Only invoked if populating the DAO with a
* SqlSessionFactory reference!
* <p>
* Can be overridden in subclasses to provide a SqlSessionTemplate instance with different configuration, or a custom
* SqlSessionTemplate subclass.
*
* @param sqlSessionFactory
* the MyBatis SqlSessionFactory to create a SqlSessionTemplate for
* @return the new SqlSessionTemplate instance
* @see #setSqlSessionFactory
*/
@SuppressWarnings("WeakerAccess")
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* Return the MyBatis SqlSessionFactory used by this DAO.
*
* @return a factory of SqlSession
*/
public final SqlSessionFactory getSqlSessionFactory() {
return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
}
/**
* Set the SqlSessionTemplate for this DAO explicitly, as an alternative to specifying a SqlSessionFactory.
*
* @param sqlSessionTemplate
* a template of SqlSession
* @see #setSqlSessionFactory
*/
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
/**
* Users should use this method to get a SqlSession to call its statement methods This is SqlSession is managed by
* spring. Users should not commit/rollback/close it because it will be automatically done.
*
* @return Spring managed thread safe SqlSession
*/
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
/**
* Return the SqlSessionTemplate for this DAO, pre-initialized with the SessionFactory or set explicitly.
* <p>
* <b>Note: The returned SqlSessionTemplate is a shared instance.</b> You may introspect its configuration, but not
* modify the configuration (other than from within an {@link #initDao} implementation). Consider creating a custom
* SqlSessionTemplate instance via {@code new SqlSessionTemplate(getSqlSessionFactory())}, in which case you're
* allowed to customize the settings on the resulting instance.
*
* @return a template of SqlSession
*/
public SqlSessionTemplate getSqlSessionTemplate() {
return this.sqlSessionTemplate;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
DaoSupport
除此之外, SqlSessionDaoSupport
还继承了DaoSupport
,而 DaoSupport
实现 InitializingBean
接口,在Spring 初始化MapperFactoryBean
对象,会触发 DaoSupport # afterPropertiesSet()
方法,从而回到DaoSupport # checkDaoConfig()
方法,在DaoSupport # checkDaoConfig()
中:
SqlSessionSupport
中会检查sqlSessionTemplate
对象是否为null,如果为null说明SqlSessionFactory
未被装配尽量,这将会导致抛出异常。MapperFactoryBean
会检查sqlSessionTemplate
所维护的Configuration
对象【Configuration
就相当于Mybatis的注册中心,里面包含所有Mybatis的配置信息,Mapper层的配置信息/元数据等】中有没有注册过mapperInterface
的Mapper层,没有就会将mapperInterface
注册进去。
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);
}
}
/**
* 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
*/
protected abstract void checkDaoConfig() throws IllegalArgumentException;
/**
* 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
*/
protected void initDao() throws Exception {
}
}
MapperFactoryBean # getObject()
当MapperFactoryBean
被Spring初始化后,就会调用MapperFactoryBean # getObject()
方法,该方法会使用sqlSessionTemplate
构建出mapperInterface
的代理对象。Spring会FactoryBean # getObject()
返回的对象封装成Bean对象,所以我们可以通过 @Autowire
自动装配进来。
该代理对象就是 org.apache.ibatis.binding.MapperProxy
对象,当调用接口方法时,MapperProxy
会从Configuration
中获取与之对应的MapperStatement
对象进行执行。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
// intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* {@inheritDoc}
*/
@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();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
// ------------- mutators --------------
/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface
* class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* Return the mapper interface of the MyBatis mapper
*
* @return class of the interface
*/
public Class<T> getMapperInterface() {
return mapperInterface;
}
/**
* If addToConfig is false the mapper will not be added to MyBatis. This means it must have been included in
* mybatis-config.xml.
* <p>
* If it is true, the mapper will be added to MyBatis in the case it is not already registered.
* <p>
* By default addToConfig is true.
*
* @param addToConfig
* a flag that whether add mapper to MyBatis or not
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* Return the flag for addition into MyBatis config.
*
* @return true if the mapper will be added to MyBatis in the case it is not already registered.
*/
public boolean isAddToConfig() {
return addToConfig;
}
}
可能会有读者疑问,为什么在 MapperScannerConfigurer
中所生成的 MapperFactoryBean
的 BeanDefnition
对象所配置的构造函数参数是一个String类型的 Mapper 层接口类名。但是 MapperFactoryBean
是只接受Class<?>对象,为啥还能成功构建出 MapperFactoryBean
对象 呢?
因为 Spring 可以自动匹配对应依赖类型,尽可能对能转换成依赖类型的对象进行转换。具体代码情况 AbstractAutowireCapableBeanFactory # createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
方法,该方法会使用ConstructResolver
找到最匹配的构造函数方法,然后通过TypeConverter
尽可能地转换构造函数参数为依赖类型。温馨提示:MapperFactoryBean
的BeanDefinition
对象的构造函数参数值通过TypeConverter
进行转换成Class<?>
其实是通过 PropertyEditorRegistry
的默认内置PropertyEditor
—ClassEditor
进行转换的。