我们在使用MyBatis的时候,会使用@MapperScan注解来指定mapper接口所在的包路径,MyBatis会自动扫描该路径下的接口,并创建mapper对象。本文先分析一下MyBatis是怎么扫描到这些接口的,下篇文章再来分析是怎么创建mapper对象的。
MapperScannerRegistrar
下面是部分@MapperScan的定义,一般我们会通过该注解的value属性指定mapper接口所在的包。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
}
注意该注解上通过@Import注解引入了MapperScannerRegistrar这个类,该类会负责往bean工厂中注册一个BeanDefinitionRegistryPostProcessor类型的bean。
关于MapperScannerRegistrar类是怎么被扫描到以及是怎么被调用到的,参考下面两篇文章。
在Spring启动期间,会调用到MapperScannerRegistrar的registerBeanDefinitions方法。
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/*
* 因为MapperScan注解中通过@Import引入了该类,所以这里可以获取到@MapperScan注解的属性值。
*/
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 注册bean
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
/*
* 创建builder,并将@MapperScan的属性值设置到builder的属性中。
* 这里创建的是MapperScannerConfigurer的bean定义,不是MapperScannerRegistrar。
* 该类被处理是在ConfigurationClassPostProcessor中,这是一个BeanDefinitionRegistryPostProcessor,
* 但是这里注入的也是一个BeanDefinitionRegistryPostProcessor,那注入的这个bean还来得及执行吗?
* 具体原因可以参考spring中PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法中的while循环,
* 在那里注明了原因。
*
*/
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
/*
* 下面在获取@MapperScan注解中的属性并设置到MapperScannerConfigurer这个bean的定义中。
*/
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));
// for spring-native
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
/*
* 这里的name值为ant.lzip.net.MyBatisTestApplication#MapperScannerRegistrar#0,
* 为什么要注册该类?
*/
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
上面两个重载方法看似很长,其实就是下面几个步骤:
- 获取@MapperScan注解的信息;
- 创建bean定义构建器,指定bean的类型为MapperScannerConfigurer;
- 将@MapperScan注解中的属性设置到bean定义构建器中;
- 生成bean定义并注册到bean工厂中;
至此MapperScannerRegistrar的使命就结束了,接下来就需要MapperScannerConfigurer来扫描mapper接口了。
实际上,是Spring中的ConfigurationClassPostProcessor先执行,然后扫描到@MapperScan注解上的@Import注解,接着调用MapperScannerRegistrar的registerBeanDefinitions方法,从而加载到MapperScannerConfigurer。接下来Spring就会调用这个bean工厂后置处理器的后置注册逻辑。
MapperScannerConfigurer
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
/*
* 创建扫描器,该类继承了Spring的ClassPathBeanDefinitionScanner。
*/
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);
}
// 注册过滤器,用于过滤掉不符合要求的mapper接口
scanner.registerFilters();
/*
* 扫描mapper接口包,这里还传入了分隔符,包含多种字符,可以拼接多个字符串。
*/
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
在该方法中,就是创建了ClassPathMapperScanner这个扫描器对象,通过它来进行mapper接口的扫描。
ClassPathMapperScanner
ClassPathMapperScanner是MyBatis中的类,继承了Spring中的ClassPathBeanDefinitionScanner。其实真正的扫描逻辑是在父类中的,MyBatis只是在子类中实现了注册过滤器和处理bean定义的逻辑。
强烈建议在阅读下面的内容之前,要熟悉ClassPathBeanDefinitionScanner是怎么工作的。如果你不熟悉请请参考Spring中的ClassPathBeanDefinitionScanner一文。
注册扫描过滤器
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) { // 默认情况下这里就是null
// 如果指定了注解类型,则只包含被这种注解修饰的类
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// default include filter that accepts all classes
// 默认会扫描所有的类,这也是为什么mapper接口可以不用加@Component或其他注解之类的
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
// 排除package-info.java文件
return className.endsWith("package-info");
});
}
重点关注第三个if块,默认是会扫描所有的类,但最后只会注册接口对应的bean,因为该类重写了isCandidateComponent方法。
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 需要是接口且不是内置在其他类中,才算是bean
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
该方法中会把单独声明的接口看做是bean类型。所以说这也是为什么不用在mapper接口上添加@Mapper注解。其实就算加了,MyBatis也不会做额外的操作,因为本来它就是一个标记接口。
扫描
在MapperScannerConfigurer类中调用了scan方法,该方法又会调用到doScan方法。ClassPathMapperScanner没有重写前者,但重写了后者。
@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 {
// 然后本类中的方法再对bean定义进行处理
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
处理bean定义
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
// 遍历bean定义
for (BeanDefinitionHolder holder : beanDefinitions) {
// 获取bean定义
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
// 如果是ScopedProxyFactoryBean
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition) Optional
.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
.map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
"The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
scopedProxy = true;
}
// 获取bean的类型名
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
// 将mapper接口类型设置到构造器的参数值中,后续实例化mapper bean时会用到,参考MapperFactoryBean的有参构造器
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
try {
// for spring-native
/*
* 这里给definition.getPropertyValues()添加属性, 后面bean实例化后属性注入的时候会用到 下面类似
*/
definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
} catch (ClassNotFoundException ignore) {
// ignore
}
/*
* ================ 重要 ===================
* 将bean类型设置为MapperFactoryBean MapperFactoryBean实现了FactoryBean接口,所以Spring会调用它的getObject方法来创建bean。
* MapperFactoryBean的getObject()方法会通过sqlSession.getMapper()来实现代理对象创建
* ========================================
*/
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// Attribute for MockitoPostProcessor
// https://github.com/mybatis/spring-boot-starter/issues/475
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
boolean explicitFactoryUsed = false;
// 设置SqlSessionFactory属性,默认下面两个if块都不满足条件
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
// 设置属性SqlSessionFactory bean的名称
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
// 设置属性SqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
// 设置SqlSessionTemplate属性,默认下面两个if块都不满足条件
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
// 设置属性SqlSessionTemplate bean名称
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
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);
if (scopedProxy) {
continue;
}
if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
definition.setScope(defaultScope);
}
if (!definition.isSingleton()) {
// 创建作用域代理
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}
在上面的方法中,最终要的是将bean的类型设置为了MapperFactoryBean,这是一个FactoryBean的实现类。Spring在创建bean的时候会调用它的getObject方法来获取bean。
总结
本文分析了Mybatis中的三个重要的类,这三个类依次协作,最终将我们编写的mapper接口扫描到bean工厂中。不过好像本文并没有分析到是怎么把mapper接口的bean定义注册到bean工厂中的,突然就结束了。实际上,本文的涉及的内容是MyBatis中与Spring中交互的一部分,所以需要熟悉很多Spring中的内容。