https://mybatis.org/spring/zh/mappers.html
mybatis-spring
为我们提供了注册Mapper接口的2种方式
-
手动配置 MapperFactoryBean
-
@MapperScan
自动扫描发现
手动的方式
@Configuration
public class MyBatisConfig {
@Bean
public MapperFactoryBean<UserMapper> userMapper() throws Exception {
MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory());
return factoryBean;
}
}
我们有几个
Mapper接口
就需要配置几个这种Bean
,相当麻烦,@MapperScan
帮我们省去了这一工作,但本质上也是自动帮我们为每个Mapper接口
注册这样的一个Bean。
MapperFactoryBean
本篇文章不会说其原理。大家只要知道,这种方式可以将我们的Mapper接口
注册成为Bean即可。 下面我们就来看下@MapperScan
是如何自动帮我们为每个Mapper接口
生成一个MapperFactoryBean
并注册到Spring容器中的。
@MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
/**
* basePackages属性的别名
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 指定扫描的包路径
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 通过类全限定名指定扫描包路径
*/
Class<?>[] basePackageClasses() default {};
/**
* @link BeanNameGenerator
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* 指定注解,被标注指定注解的接口才会被看做成Mapper接口被扫描
*/
Class<? extends Annotation> annotationClass() default Annotation.class;
/**
* 指定接口类型,被指定的接口及其子接口才会被看做成 Mapper接口被扫描
*/
Class<?> markerInterface() default Class.class;
/**
* SqlSessionTemplate的beanName指定
*/
String sqlSessionTemplateRef() default "";
/**
* sqlSessionFactory的beanName指定
*/
String sqlSessionFactoryRef() default "";
/**
* 指定MapperFactoryBean类型,允许用户自定义但需要继承MapperFactoryBean
*/
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
/**
* 是否延迟初始化
*/
String lazyInitialization() default "";
/**
* 指定默认的Scope
*/
String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;
}
下面我们查看一下
@Import
引入的类MapperScannerRegistrar
MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
@Deprecated
public void setResourceLoader(ResourceLoader resourceLoader) {
// NOP
}
@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);
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);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList<>();
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()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
String defaultScope = annoAttrs.getString("defaultScope");
if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
builder.addPropertyValue("defaultScope", defaultScope);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
}
private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
return ClassUtils.getPackageName(importingClassMetadata.getClassName());
}
static class RepeatingRegistrar extends MapperScannerRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScansAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
if (mapperScansAttrs != null) {
AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
for (int i = 0; i < annotations.length; i++) {
registerBeanDefinitions(importingClassMetadata, annotations[i], registry,
generateBaseBeanName(importingClassMetadata, i));
}
}
}
}
}
MapperScannerRegistrar
实现了ImportBeanDefinitionRegistrar
,自然就看其registerBeanDefinitions
方法,这个方法注册了一个BeanMapperScannerConfigurer
,我们看看其流程
-
获取 MapperScannerConfigurer
的BeanDefinitionBuilder
-
设置属性 processPropertyPlaceHolders
为true
-
设置属性 annotationClass
为@MapperScan#annotationClass
-
设置属性 markerInterface
为@MapperScan#markerInterface
-
设置属性 nameGenerator
为@MapperScan#nameGenerator
-
设置属性 mapperFactoryBeanClass
为@MapperScan#factoryBean
-
设置属性 sqlSessionTemplateBeanName
为@MapperScan#sqlSessionTemplateRef
-
设置属性 sqlSessionFactoryBeanName
为@MapperScan#sqlSessionFactoryRef
-
设置属性 lazyInitialization
为@MapperScan#lazyInitialization
-
设置属性 defaultScope
为@MapperScan#defaultScope
-
设置属性 basePackage
-
设置 BeanDefinition
角色 -
注册 MapperScannerConfigurer
的BeanDefinition
最终注册了
MapperScannerConfigurer
扫描包的设置
@MapperScan
的作用就是注册MapperScannerConfigurer
,其中会指定扫描包,我们来看一下其扫描包怎么选取的
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
//.....................
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()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
//..............
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
-
读取 @MapperScan#basePackages
中的数据 -
读取 @MapperScan#basePackageClasses
中的数据,并通过ClassUtils::getPackageName
解析成包名 -
若最终 basePackages
变量为空(@MapperScan
既没有指定basePackages
也没有指定basePackageClasses
),则通过getDefaultBasePackage
设置扫描包
ClassUtils::getPackageName
会将指定的类所在的包作为扫描包
public static String getPackageName(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
return getPackageName(clazz.getName());
}
public static String getPackageName(String fqClassName) {
Assert.notNull(fqClassName, "Class name must not be null");
int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
}
getDefaultBasePackage(annoMeta)
若没指定扫描包,@MapperScan
标注的类所在的包作为扫描包
private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
return ClassUtils.getPackageName(importingClassMetadata.getClassName());
}
public static String getPackageName(String fqClassName) {
Assert.notNull(fqClassName, "Class name must not be null");
int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
}
MapperScannerConfigurer
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
/**
* {@inheritDoc}
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//......
}
}
可以看到
MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
,因此会在Spring应用上下文刷新时invokeBeanFactoryPostProcessors(beanFactory);
方法中被调用。
MapperScannerConfigurer#postProcessBeanDefinitionRegistry
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
private boolean addToConfig = true;
private String lazyInitialization;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionFactoryBeanName;
private String sqlSessionTemplateBeanName;
private Class<? extends Annotation> annotationClass;
private Class<?> markerInterface;
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;
private ApplicationContext applicationContext;
private String beanName;
private boolean processPropertyPlaceHolders;
private BeanNameGenerator nameGenerator;
private String defaultScope;
@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));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
代码相当易懂,就是创建
ClassPathMapperScanner
对象并调用scan
方法进行扫描包
ClassPathMapperScanner
package org.mybatis.spring.mapper;
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
}
package org.springframework.context.annotation;
public class ClassPathBeanDefinitionScanner
extends ClassPathScanningCandidateComponentProvider {
}
可以看到
ClassPathMapperScanner
是mybatis-spring
框架继承Spring的ClassPathBeanDefinitionScanner
所实现的类
ClassPathBeanDefinitionScanner#scan
ClassPathMapperScanner
并没有重写ClassPathBeanDefinitionScanner
的scan方法
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
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);
}
}
这段代码的逻辑是:
前后获取 BeanDefinition
的数量返回差值AnnotationConfigUtils.registerAnnotationConfigProcessors
:用于注册一些注解相关的BeanPostProcessor
最后看 doScan
ClassPathMapperScanner#doScan
@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(basePackages)
:就是调用ClassPathBeanDefinitionScanner#doScan
默认情况下
ClassPathBeanDefinitionScanner#doScan
不会扫描接口类,通过ClassPathScanningCandidateComponentProvider#isCandidateComponent()
过滤掉扫描包内的所有接口,但是ClassPathMapperScanner重写了isCandidateComponent
,只扫描所有接口类。因此beanDefinitions
集合中是所有接口类型的BeanDefinition
-
执行 ClassPathMapperScanner#processBeanDefinitions
ClassPathMapperScanner#processBeanDefinitions
这里再把手动注册
MapperFactoryBean
的方法放在这边,可以看到MapperFactoryBean
需要一个mapperInterface
@Configuration
public class MyBatisConfig {
@Bean
public MapperFactoryBean<UserMapper> userMapper() throws Exception {
MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory());
return factoryBean;
}
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
}
我们看一下
ClassPathMapperScanner#processBeanDefinitions
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
try {
// for spring-native
definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
} catch (ClassNotFoundException ignore) {
}
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
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) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
//...........
}
}
}
上面代码我也是截取了部分好理解的,遍历所有接口的
BeanDefinition
-
获取 beanClassName
并设置在BeanDefinition
的mapperInterface
属性中 -
重新设置 BeanDefiniton
的beanClass
属性为mapperFactoryBeanClass
-
设置 sqlSessionFactory
或者sqlSessionTemplate
到BeanDefinition
中
sqlSessionFactory
和sqlSessionTemplate
必须被设置一个,否则会开启byType自动装配sqlSessionFactory
和sqlSessionTemplate
同时配置时,sqlSessionFactory
会被sqlSessionTemplate
取代
上面代码就是偷天换日,本来扫描到的
BeanDefinition
的class类型为XxxMapper接口.class
。最后被换成MapperFactoryBean
类型,并且XxxMapper接口.class
被设置到mapperInterface
属性中。
总结
好的,@MapperScan
的原理就聊到这里,细节方面我们不去深究,我们可以总结一下其大致原理。
-
mybatis-spring
框架通过@MapperScan
引入MapperScannerConfigurer
-
MapperScannerConfigurer
在容器刷新时,通过ClassPathMapperScanner
扫描指定包下的所有接口,生成接口对应BeanDefinition
-
将所有扫描到接口对应 BeanDefinition
的beanClass
全部换成mapperFactoryBeanClass
,并设置mapperInterface
为相应接口类型。 -
最终,注册的是一个个的 MapperFactoryBean
类型。
原文始发于微信公众号(溪溪技术笔记):Spring整合MyBatis原理-@MapperScan
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/207129.html