前言
说到Mybatis
想必大家都很熟悉,作为和Spring
结合的非常紧密的一个数据库插件,其提供的便利性非常值得我们去学习。研读Mybatis-Spring
的第一步就是要明白Mybatis
是怎么把Mapper
接口转化为MapperFactoryBean
的,如果不了解这一步,基本上读源码也会读的知其然不知所以然。由于网上很多讲解MapperScan
注解的都已经过时了,而Mybati-Spring
的新版本已经对代码做了重大的修改,所以笔者根据比较新的mybatis-spring:2.0.5
版本进行一个重新的解读,和大家一起看下新版中Mybatis-Spring
做了什么样的更新。更多Spring内容进入【Spring解读系列目录】。
@MapperScan注解
说到这个注解,最直观的印象就是它能把我们配置的包里面的Mapper接口转化为对应的代理类。其实笔者之前介绍Spring中ImportBeanDefinitionRegistrar
类的时候已经对这一部分的原理有过详细的讲解,回顾点击这里【Spring框架中ImportBeanDefinitionRegistrar的应用】。但是Mybatis-Spring
的新版本中有了一些大的更新,做了一个更加详细的封装,但是换汤不换药,所以如果对下面讲的Spring原理有疑惑的同学,可以去上面的帖子里补习一下。
@MapperScan注解源码解析
注解能够运行,是因为在@MapperScan
上有一个更强大的注解@Import(MapperScannerRegistrar.class)
,MapperScannerRegistrar
这个类结合Spring做了所有的工作,使得我们可以方便的使用。它所实现的ImportBeanDefinitionRegistrar
接口中的registerBeanDefinitions()
方法使得外部有机会去干扰Spring的运行,也给Mybatis
的实现提供了机会。那么就进入这个类的registerBeanDefinitions()
方法看看里面写了什么:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//首先拿到MapperScan注解的注解
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) { //如果拿到了就进入自己的registerBeanDefinitions方法
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
相信看过Mybatis
老版本源码的读者一定记得这个registerBeanDefinitions()
方法里,将会使用doScan()
方法扫描Mapper
接口,并且做成MapperBeanFactor
注册到Spring容器中,但是如今时代变了。
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
//创建BeanDefinitionBuilder,拿出来MapperScannerConfigurer的BeanDefinition信息
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("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
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);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
//被注册了
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
可以看到这个方法里已经不再有doScan()
的方法,但是注意到最后一句发现最终被注册到Spring容器中的是一个builder
的对象。往上翻发现这个builder
是从MapperScannerConfigurer
类中拿出来的BeanDefinition
的信息。也就是说自此Spring正式接管了后续的创建过程,所以就必须去看下MapperScannerConfigurer
类长什么样子:
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor,
InitializingBean, ApplicationContextAware, BeanNameAware {
//......
}
熟悉Spring框架的同学,一定可以进来就发现一个老朋友BeanDefinitionRegistryPostProcessor
。这个类是BeanFactoryPostProcessor
子类,也就是通过实现这个接口我们能够获取并改变一个Bean的实例过程,也就是一个Bean的扩展点。那就直接找到他的实现方法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);
//设置MapperFactoryBeanClass
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));
}
到了这里就有点意思了,新版本的Mybatis把自己的扫描类ClassPathMapperScanner
给藏到了这里,其中有一点比较值得注意scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
给扫描出来的类设置一个MapperFactoryBean
。点进去这个set
方法:
public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
}
如果这个的mapperFactoryBeanClass
是null
,则把MapperFactoryBean.class
赋值给它,也就是说这个属性必须是MapperFactoryBean
类型的。这里先放一放,接着往下走去scanner.scan()
方法:
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);
}
在scan()
方法里我们发现了老朋友doScan()
方法,继续往里走:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//首先用父类的doScan()把所有指定包的mapper接口的BeanDefinition拿出来,
// 不是主要部分,大家自己可以看是怎么转换的
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;
}
在这个方法里我们拿出来了所有MapperScan
注解下指定的Mapper
接口,并且将其传入了processBeanDefinitions(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");
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);
}
}
这里对我们的Mapper进行了各种重定义,但是都不重要,最最重要的一句话就在这里definition.setBeanClass(this.mapperFactoryBeanClass);
也就是说,如果我们定义的Mapper
接口未来要表达的话,就使用MapperFactoryBean
这个类进行。而这个类大有文章,这个类实现了FactoryBean
接口,这样就能够通过Spring容器直接进行一个代理,并通过其中的getObject()
方法拿到具体的代理对象(知识点回顾【实例区别BeanFactory和FactoryBean】),然后由代理对象处理具体的Sql
语句的执行。
有什么好处
分析到这里就可以返回了,想一下为什么Mybatis要这么做,而不是直接在MapperScannerRegistrar#registerBeanDefinitions()
里面继续直接使用doScan()
?笔者目前考虑来说其实有三大好处。
- 符合编码的单一职责原则。这个方法既然是Spring定义的注册bean的方法,那么就只让这个方法执行注册的功能。
- 未来修改有优势。由于这里只是注册了bean,那么其开放程度就远远的大于原来的写法,如果用户想要对这个类进行修改那么,或者未来对这个类进行增强,那么修改的幅度会远远小于直接打开源代码修改。
- 解耦。减少对Spring框架的依赖,Mybatis只对Bean进行了注册,如果未来换了一个框架,Mybatis只需要修改注册的方式就可以完成对新框架的支持。
总结
通过分析源码可以得出@MapperScan主要做了三个工作:
- 扫描出所有的Mapper接口所对应的BeanDefinition。
- 把Mapper接口转换为FactoryBean,或者说MapperFactoryBean的BeanDefinition
- 为BeanDefinition添加一个构造方法的值,并使用这个Class,在Spring示例化过程中根据这个Class返回相对应的代理对象。
本篇博客解释了在新的版本中Mybatis
是如何把Mapper
接口转化为一个代理对象的过程,换句话说就是解决了Mapper
接口到一个Bean
的过程。下一篇【Mybatis-Spring源码分析(二) Mapper接口代理的生成】会着重讲解一下Mybatis
再运行时是怎么使用代理去完成sql语句的执行。