Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析
文章目录
系列相关相关文章 |
---|
究竟FactoryBean是什么?深入理解Spring的工厂神器 |
超硬核解析Mybatis动态代理原理!只有接口没实现也能跑? |
Mybatis与Spring结合深探——MapperFactoryBean的奥秘 |
Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析 |
引言
在使用Spring+Mybatis开发数据访问层时,我们通常需要定义一些数据映射器接口(Mapper Interface),用来执行SQL语句并返回结果。为了让Spring能够管理这些映射器接口,我们需要将它们注入到service层的bean中,这样就可以在service层调用映射器接口的方法来操作数据库。
为了实现这一目的,Mybatis提供了两种方式:MapperFactoryBean和MapperScannerConfigurer。这两种方式都可以让Spring识别和管理映射器接口,但是它们的使用方法和原理有所不同。本文将对比分析这两种方式的优缺点,以及MapperScannerConfigurer的源码实现。
至于MapperFactoryBean的源码实现:Mybatis与Spring结合深探——MapperFactoryBean的奥秘,在之前的文章已有解析,这里再来简单回顾一下
MapperFactoryBean的用法和优缺点
MapperFactoryBean是一个实现了FactoryBean接口的类,它可以用来创建和初始化一个映射器接口的代理对象,并将其注册到Spring容器中。我们可以在Spring的配置文件中为每个映射器接口定义一个MapperFactoryBean的bean,指定其映射器接口的类型和sqlSessionFactory的引用,例如:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.example.mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
这样,我们就可以在service层的bean中注入userMapper这个bean,然后调用其方法来执行SQL语句,例如:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserById(int id) {
return userMapper.getUserById(id);
}
}
使用MapperFactoryBean的优点是:
- 简化了DAO层的编写,不需要再为每个映射器接口编写实现类,只需要定义接口和SQL映射文件即可。
- 自动管理了sqlSession的开启和关闭,不需要手动编写try-catch-finally代码,也不需要担心sqlSession的泄露和性能问题。
- 可以给予开发者更多控制权,适用于需要对各个Mapper做特别配置的场景。
使用MapperFactoryBean的缺点是:
- 需要为每个映射器接口手动配置一个bean,如果映射器接口很多,那么配置文件会很冗长和繁琐,也不利于维护和修改。
MapperScannerConfigurer的用法和优缺点
上面说到使用MapperFactoryBean的缺点是:需要为每个映射器接口手动配置一个bean,如果映射器接口很多,那么配置文件会很冗长和繁琐,也不利于维护和修改。为了解决这个问题,我们可以使用MapperScannerConfigurer,让它扫描特定的包,自动帮我们成批的创建映射器。这样一来,就能大大减少配置的工作量。
MapperScannerConfigurer是一个实现了BeanDefinitionRegistryPostProcessor接口的类,它可以用来自动扫描和注册映射器接口,而不需要为每个映射器接口配置一个bean。我们可以在Spring的配置文件中定义一个MapperScannerConfigurer的bean,指定其要扫描的包名和sqlSessionFactory或sqlSessionTemplate的bean名称,例如:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
这样,MapperScannerConfigurer会在Spring容器初始化时,扫描指定包下的所有映射器接口,并为每个接口创建一个MapperFactoryBean的bean,然后将其注册到Spring容器中。我们就可以在service层的bean中直接注入映射器接口的类型,然后调用其方法来执行SQL语句,例如:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserById(int id) {
return userMapper.getUserById(id);
}
}
使用MapperScannerConfigurer的优点是:
- 进一步简化了配置,不需要为每个映射器接口配置一个bean,只需要配置一个MapperScannerConfigurer的bean即可,可以减少配置文件的长度和复杂度,也更容易修改和维护。
- 支持多种过滤条件,可以根据注解、接口、父类等条件来过滤和选择要扫描的映射器接口,可以更灵活地控制映射器接口的注册。
MapperFactoryBean源码分析
关于MapperFactoryBean的分析,可以查看之前的文章:Mybatis与Spring结合深探——MapperFactoryBean的奥秘,这里我们重点讲解MapperScannerConfigurer
MapperScannerConfigurer源码分析
Spring容器初始化流程回顾
如果对于
BeanDefinitionRegistryPostProcessor
不熟的话,这里需要学习下Spring容器初始化的流程了才更能理解这里的内容,这里我简单提下ApplicaitonContext
容器大致关键的步骤。
MapperScannerConfigurer这个类实现了如下接口,用来对扫描的Mapper进行处理:
- BeanDefinitionRegistryPostProcessor
- InitializingBean
- ApplicationContextAware
- BeanNameAware
执行流程如下:
- 读取配置文件,将标签解析成一个个的
BeanDefinition
- 执行
BeanDefinitionRegistryPostProcessor
接口的postProcessBeanDefinitionRegistry
方法。 - 执行
BeanFactoryPostProcessor
接口的postProcessBeanFactory
方法,在这里可以扩展修改第一步的BeanDefinition
。 - 初始化bean实例(调用构造方法)。
- 调用setter方法设置属性。
- 执行
BeanPostProcessor
接口的postProcessBeforeInitialization
方法。 - 执行
InitializingBean
接口的afterPropertiesSet
方法。 - 执行bean的init-method。
- 执行
BeanPostProcessor
接口的postProcessAfterInitialization
方法。
从上述容器的初始化流程中,可知Spring解析完配置文件生成一个个BeanDefinition
后,便会调用所有实现了BeanDefinitionRegistryPostProcessor
接口的bean中的postProcessBeanDefinitionRegistry
方法。因此MapperScannerConfigurer
中的postProcessBeanDefinitionRegistry
将会得到执行。
值得注意的一个点就是对于实现了BeanDefinitionRegistryPostProcessor
或者BeanFactoryPostProcessor
的bean来说,要向执行这两个接口的方法,需要先有bean的实例。因此会先创建实例,这样就会导致4-9这些步骤提早触发。因此MapperScannerConfigurer
的执行流程为
- 构造方法实例化
- 调用setter方法设置属性
- 调用
setBeanName
以及setApplicationContext
初始化beanName
以及applicationContext
属性,Aware接口里的方法是由ApplicationContextAwareProcessor
回调的,ApplicationContextAwareProcessor
是一个BeanPostProcessor
,回调逻辑是在postProcessBeforeInitialization
方法中 - 执行
afterPropertiesSet
方法 - 执行
postProcessBeanDefinitionRegistry
方法 - 执行
postProcessBeanFactory
方法
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
/** 需要扫描的包,多个包名可以使用逗号分割 **/
private String basePackage;
/** 同MapperFactoryBean **/
private boolean addToConfig = true;
/**
* 被sqlSessionFactoryBeanName取代
* 被取代的原因放在总结里,如果好奇可以看下
*/
private SqlSessionFactory sqlSessionFactory;
/** 被sqlSessionTemplateBeanName取代 **/
private SqlSessionTemplate sqlSessionTemplate;
/** SqlSessionFactory的名字 **/
private String sqlSessionFactoryBeanName;
/**
* sqlSessionTemplate的名字,创建MapperFactoryBean时需要依赖
* 一个SqlSessionFactory或者sqlSessionTemplate实例。
*/
private String sqlSessionTemplateBeanName;
/** 扫描条件,只有接口被此注解标注才会被注册,默认为null **/
private Class<? extends Annotation> annotationClass;
/** 扫描条件,只有接口继承了指定的这个接口才会被注册,默认为null **/
private Class<?> markerInterface;
private ApplicationContext applicationContext;
/** 代表MapperScannerConfigurer这个bean的名字 **/
private String beanName;
/**
* 如果为true, 代表MapperScannerConfigurer配置使用了${}表达式
* 需要先解析${}表达式
* 因此如果配置MapperScannerConfigurer时有使用${}表达式,需要将这个值设置成true
*/
private boolean processPropertyPlaceHolders;
/**
* 注册接口时使用这个字段来生成bean的名字
*/
private BeanNameGenerator nameGenerator;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// left intentionally blank
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
// 处理占位符,processPropertyPlaceHolders默认为false
// 因为MapperScannerConfigurer先于Spring中处理占位符的BeanFactoryPostProcessor执行
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);
// 注册TypeFilter,也就是筛选条件
scanner.registerFilters();
// 扫描包,多个包名可以使用逗号分割
scanner.scan(StringUtils.tokenizeToStringArray(
this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
@Override
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
// other methods and setters and getters ...
}
-
从源码中可以看出,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,InitializingBean,ApplicationContextAware和BeanNameAware这四个接口。它有很多的属性,其中最重要的是basePackage,表示要扫描的包名,以及sqlSessionFactoryBeanName和sqlSessionTemplateBeanName,表示sqlSessionFactory或sqlSessionTemplate的bean名称。其他的属性都是用来设置扫描的过滤条件和MapperFactoryBean的属性的。
-
MapperScannerConfigurer实现了InitializingBean接口的afterPropertiesSet方法,该方法是在Bean的属性设置完成后调用的,用来检查和初始化Bean的状态。在该方法中,MapperScannerConfigurer只做了一件事,就是判断了basePackage是否为空,如果为空则抛出异常,如果不为空则什么也不做。这是因为basePackage是MapperScannerConfigurer的必须属性,如果没有设置basePackage,那么MapperScannerConfigurer就无法扫描任何映射器接口,所以要在这里进行检查。
-
MapperScannerConfigurer还实现了ApplicationContextAware和BeanNameAware这两个接口,这两个接口都是用来让Bean获取到Spring容器的上下文信息的。ApplicationContextAware接口的方法是setApplicationContext,用来设置applicationContext的引用,这样就可以访问Spring容器中的其他Bean或资源。BeanNameAware接口的方法是setBeanName,用来设置beanName的值,这样就可以知道自己在Spring容器中的名称。MapperScannerConfigurer实现了这两个接口的方法,分别将applicationContext和beanName的值保存到了自己的属性中,以便于在后续的操作中使用。
核心方法:postProcessBeanDefinitionRegistry
BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor
-
MapperScannerConfigurer重写了postProcessBeanFactory和postProcessBeanDefinitionRegistry这两个方法,这两个方法都是BeanDefinitionRegistryPostProcessor接口的方法,用来在BeanDefinition阶段对Bean的定义进行修改或注册。在postProcessBeanFactory方法中,MapperScannerConfigurer什么也没有做,这是因为它不需要在这个阶段对Bean的定义进行修改,而是在postProcessBeanDefinitionRegistry方法中进行注册。
-
BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor这两者区别总结如下:更详细的可以自己另外查询资料
BeanDefinitionRegistryPostProcessor
允许在BeanFactoryPostProcessor
之前注册其他的BeanDefinition
,这个是重中之重!也就是BeanDefinitionRegistryPostProcessor
的执行时机比BeanFactoryPostProcessor
更早,BeanFactoryPostProcessor
一般只用来修改、扩展BeanDefinition
中的信息,而BeanDefinitionRegistryPostProcessor
则可以在BeanFactoryPostProcessor
处理BeanDefinition
之前,向BeanFactory
注册新的BeanDefinition
,甚至注册新的BeanFactoryPostProcessor
用于下一个阶段的回调。
这段话干说可能很难理解,我们来借助一张图理解:
这样就好理解多了吧!不过实际执行的机制跟上面的图还有一点点小差别:由于实现了 BeanDefinitionRegistryPostProcessor
的类同时也实现了 BeanFactoryPostProcessor
的 postProcessBeanFactory
方法,所以在执行完所有 BeanDefinitionRegistryPostProcessor
的接口方法后,会立即执行这些类的 postProcessBeanFactory
方法,之后才是执行那些普通的只实现了 BeanFactoryPostProcessor
的 postProcessBeanFactory
方法。
执行步骤
调用链路如下所示:
MapperScannerConfigurer#postProcessBeanDefinitionRegistry-->ClassPathBeanDefinitionScanner#registerFilters-->ClassPathBeanDefinitionScanner#scan(basePackages)-->ClassPathBeanDefinitionScanner#doScan-->ClassPathBeanDefinitionScanner#processBeanDefinitions-:核心目的就是definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName)和definition.setBeanClass(this.mapperFactoryBeanClass)
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//该方法会解析配置文件中的占位符,例如${…},并替换为对应的值,这样就可以动态地设置basePackage等属性的值。
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHol·ders();
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
// 处理占位符,processPropertyPlaceHolders默认为false
// 因为MapperScannerConfigurer先于Spring中处理占位符的BeanFactoryPostProcessor执行
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);
// 注册TypeFilter,也就是筛选条件
scanner.registerFilters();
// 扫描包,多个包名可以使用逗号分割
scanner.scan(StringUtils.tokenizeToStringArray(
this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
1、在postProcessBeanDefinitionRegistry方法中,MapperScannerConfigurer首先判断了processPropertyPlaceHolders是否为true,如果是则调用processPropertyPlaceHolders方法,该方法会解析配置文件中的占位符,例如${…},并替换为对应的值,这样就可以动态地设置basePackage等属性的值。
2、接下来,MapperScannerConfigurer创建了一个ClassPathMapperScanner对象,该对象是一个继承了ClassPathBeanDefinitionScanner的类,用来扫描指定路径下的类,并根据过滤条件生成和注册Bean的定义。
3、MapperScannerConfigurer将自己的属性值都设置到了ClassPathMapperScanner对象中,然后调用了其registerFilters方法,该方法会根据annotationClass,markerInterface等属性,创建相应的TypeFilter对象,并添加到ClassPathMapperScanner对象的过滤器列表中,这样就可以根据不同的条件来过滤和选择要扫描的映射器接口。
// 注册TypeFilter
public void registerFilters() {
boolean acceptAllInterfaces = true;
// 添加一个AnnotationTypeFilter,需要接口被此注解标注
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// 添加一个AssignableTypeFilter,需要接口继承markerInterface
// AssignableTypeFilter会接受参数本身以及参数的派生类
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
// 这里是去掉markerInterface自己
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
// 如果没有指定TypeFilter筛选,注册一个接受报下所有接口的TypeFilter
if (acceptAllInterfaces) {
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory)
throws IOException {
return true;
}
});
}
// exclude package-info.java
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
4、最后,MapperScannerConfigurer调用了ClassPathMapperScanner对象的scan方法,该方法会根据basePackage的值,扫描指定包下的所有类,然后根据过滤器列表,筛选出符合条件的映射器接口,为每个映射器接口创建一个MapperFactoryBean的BeanDefinition对象,并注册到BeanDefinitionRegistry中,这样就完成了映射器接口的自动扫描和注册。
ClassPathBeanDefinitionScanner的scan方法解析
ClassPathBeanDefinitionScanner#scan(basePackages)-->ClassPathBeanDefinitionScanner#doScan-->ClassPathBeanDefinitionScanner#processBeanDefinitions-:核心目的就是definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName)和definition.setBeanClass(this.mapperFactoryBeanClass)
// 这个完全就是父类ClassPathBeanDefinitionScanner的方法,以前讲过,因此不再展开说了,
// 主要关注子类重写的doScan方法
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
doScan方法:
-
优先调用父类的super.doScan(basePackages),进行注册Bean信息,用于Mapper扫描和Bean的定义以及注册到DefaultListableBeanFactory。{DefaultListableBeanFactory是Spring中IOC容器的始祖,所有需要实例化的类都需要注册进来,之后在初始化}
-
findCandidateComponents(basePackage),扫描package包路径,对于注解类的有另外的方式,大同小异
-
registerBeanDefinition(definitionHolder, this.registry);注册Bean信息的过程,最终会调用到:org.springframework.beans.factory.support.DefaultListableBeanFactory
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类ClassPathBeanDefinitionScanner的doScan方法
// 获取所有接口的BeanDefinition,此时这些BeanDefinition已经被注册到容器中了
// 但是还没有实例化,因此可以对这些BeanDefinition做些修改
// 接下来就要对一个个的BeanDefinition加工了
// 将这一个个的BeanDefinition转成代表BeanFactoryBean的BeanDefinition
// BeanFactoryBean会为接口生成代理实现
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;
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
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.regi
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
processBeanDefinitions方法:
-
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 设置BeanName参数,也就是我们的:ISchoolDao、IUserDao
-
definition.setBeanClass(this.mapperFactoryBean.getClass());修改beanClass为MapperFactoryBean.class,接口本身是没有类的,那么这里将MapperFactoryBean类设置进来,最终所有的dao层接口类都是这个MapperFactoryBean,通过MapperFactoryBean的getObject()方法就能获取动态代理对象
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" +
holder.getBeanName() + "' and '" +
definition.getBeanClassName() + "' mapperInterface");
}
// 构造方法注入接口名字,此时definition.getBeanClassName()获取的是接口的完全限定名,如果看的仔细点知道MapperFactoryBean构造方法参数是接口Class对象,为什么传字符串就行了呢? 这是Spring在初始化实例时会将字符串根据PropertyEditor转化规则转成相对应的类型,将String -> Class的
//PropertyEditor实现ClassEditor.在初始化SqlSessionFactoryBean时指定映射文件,也用的是字符串,但实际类型是Resource
// 将String -> Resource是ResourceEditor的功能
definition.getConstructorArgumentValues()
.addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 修改beanClass为MapperFactoryBean.class,接口本身是没有类的,那么这里将MapperFactoryBean类设置进来,最终所有的dao层接口类都是这个MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 属性注入addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
// 属性注入sqlSessionFactory
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
// 加入this.sqlSessionFactoryBeanName的值为sqlSessionFactory,那么
// 相当于<property="sqlSessionFactory" ref="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)) {
// 同时使用sqlSessionTemplate、sqlSessionFactory时,根据sqlSessionFactory
// 创建的sqlSession将被覆盖掉,这点可以去看MapperFactoryBean父类
// SqlSessionDaoSupport的setSqlSessionFactory以及setSqlSessionTemplate方法
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;
}
// SqlSessionFactory和sqlSessionTemplate都没有指定,开启自动根据类型注入。
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with"
+ "name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
MapperScannerConfigurer和MapperFactoryBean的区别、联系
从上面的源码分析中,我们可以看出,MapperFactoryBean和MapperScannerConfigurer的区别和联系如下:
- 区别:
- MapperFactoryBean是一个实现了FactoryBean接口的类,它用来创建和初始化一个映射器接口的代理对象,并将其注册到Spring容器中。MapperScannerConfigurer是一个实现了BeanDefinitionRegistryPostProcessor接口的类,它用来自动扫描和注册映射器接口,而不需要为每个映射器接口配置一个bean。
- MapperFactoryBean是在Bean实例化阶段执行的,它可以获取到Bean的引用,所以它可以直接设置sqlSessionFactory或sqlSessionTemplate的引用。MapperScannerConfigurer是在BeanDefinition阶段执行的,它不能获取到Bean的引用,所以它只能设置sqlSessionFactory或sqlSessionTemplate的bean名称。
- MapperFactoryBean需要为每个映射器接口手动配置一个bean,如果映射器接口很多,那么配置文件会很冗长和繁琐,也不利于维护和修改。MapperScannerConfigurer只需要配置一个bean,就可以自动扫描和注册映射器接口,可以减少配置文件的长度和复杂度,也更容易修改和维护。
- MapperFactoryBean不支持过滤条件,它会将所有配置的映射器接口都添加到Mybatis的配置中,不管它们是否有注解或继承了某个接口。MapperScannerConfigurer支持多种过滤条件,它可以根据注解、接口、父类等条件来过滤和选择要扫描的映射器接口,可以更灵活地控制映射器接口的注册。
- 联系:
- MapperFactoryBean和MapperScannerConfigurer都是用来让Spring识别和管理映射器接口的,它们都是Mybatis-Spring的核心组件,它们都依赖于sqlSession或sqlSessionTemplate的引用,它们都会将映射器接口添加到Mybatis的配置中,它们都会为映射器接口创建代理对象,并返回给service层。
- MapperScannerConfigurer实际上是基于MapperFactoryBean的,它在扫描映射器接口时,会为每个映射器接口创建一个MapperFactoryBean的BeanDefinition对象,并注册到Spring容器中,所以MapperScannerConfigurer的最终效果和MapperFactoryBean是一样的,只是它省去了手动配置的步骤,而是通过自动扫描和过滤来实现。
总结
1. MapperScannerConfigurer
是一个包扫描的配置类,其中basePackage
属性是必需的,多个包时可以使用逗号隔开。sqlSessionFactoryBeanName
、sqlSessionTemplateBeanName
二选一即可。如果都选了那么根据sqlSessionFactoryBeanName
引用的SqlSessionFactory
创建的SqlSession
会被覆盖掉。如果两个都没配置,将会根据类型自动装配,这种方式适合应用程序中只有一个SqlSessionFactory
或者SqlSessionTemplat
的bean存在。其它配置就不总结了。
2. 具体实现扫描以及注册接口的功能被委托给ClassPathMapperScanner
类了。具体做法就是将Spring扫描接口后生成的BeanDefinition
修改成一个表示MapperFactoryBean
的BeanDefinition
,而MapperFactoryBean
是一个实现FactoryBean
的特殊bean,相当于返回Mapper接口实例的工厂。具体返回接口实例的原理是MyBatis本身的接口绑定功能,底层其实是动态代理。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!