上篇笔记主要记录了BeanDefinitionRegistryPostProcessor扩展机制的实现原理,这篇笔记注意记录下,自己在学习源码的过程中,看到的该扩展机制使用的地方
其实我上篇笔记有说过,这个扩展机制,太靠前,如果我们通过@Component注解去注入到spring容器中,那我们自定义的实现类是在所有的业务bean还没有放到spring容器中的时候,就执行了,此时一般也很少有业务需求是在这个时候对beanDefinition进行一些修改,但是,在底层框架中,有可能会用到这个扩展点,我看到的是mybatis底层有用到这个扩展点
mybatis是干什么的
其实mybatis就是一个持久层框架,在和spring整合的时候,简单来说,我觉得就做了一件事情,将我们程序员自己写的mapper接口交给spring去管理
但是需要想一下,一个接口,怎么交给spring去管理?这里面的一个设计思想,我觉得是值得学习的:对于spring来说,不会为了去整合mybatis而去修改自己的代码,那原生的mybatis会修改吗?当然也不会,因为mybatis如果只是为了整合spring,修改了自己的代码,那mybatis就不是mybatis了,那怎么办呢?两者想要结合,总要有一个中间粘合剂,那这时候,spring就说了,你既然想和我进行整合,那你自己去扩展一个jar包,专门负责和我spring整合用,你可以利用我暴露给你的扩展点,总之,我不管你怎么做,你只要把你的接口放到我的beanDefinitionMap中,就可以了,我负责去初始化,然后放到容器中,至于你怎么把mapper接口放到我的beanDefinitionMap,我不管
那这时候,mybatis就自己去实现类一个jar包:mybatis-spring-1.3.2.jar
也就是在这个jar包中,mybatis利用了spring的BeanDefinitionRegistryPostProcessor这个扩展点,去把我们程序员提供的mapper接口,放入到了beanDefinitionMap中
mybatis整合spring原理
我们在使用mybatis的时候,需要加一个注解,@MapperScan,在该注解中,指定要扫描的路径,这个路径是给谁用的?就是给我们这篇笔记要学习的BeanDefinitionRegistryPostProcessor的实现类MapperScannerConfigurer
@MapperScan
在该注解中,通过@Import引入了一个类MapperScannerRegistrar,
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
这个类是ImportBeanDefinitionRegistrar的实现类,这也是spring扩展点之一,我们这里不做深入说明,我们可以简单理解为,spring在初始化的过程中,会调用该接口实现类的方法
在mybatis-spring-1.3.x.jar包中,直接在MapperScannerRegistrar 的registerBeanDefinitions()方法中,进行了扫描,去扫描我们自己声明的mapper接口
但是在mybatis-spring-2.x.jar包中,在MapperScannerRegistrar 的registerBeanDefinitions()方法中,注册到beanDefinitionMap中一个BeanDefinitionRegistryPostProcessor的实现类:MapperScannerConfigurer
所以,在1.x版本中,进行扫描的时候,使用的是ImportBeanDefinitionRegistrar这个扩展点,但是在2.x的包中,使用的是BeanDefinitionRegistryPostProcessor的扩展点
我们来看下2.X版本中是如何进行mapper扫描的
MapperScannerConfigurer
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
可以看到,在其方法中,会自己声明一个scanner,然后去调用scan方法扫描,截止到这里,我们可以结束了,已经看到了,在mybatis-spring-2.x.jar中,就是利用了BeanDefinitionRegistryPostProcessor的机制,去进行mapper的扫描
扫描之后的后续处理
由于这里的ClassPathMapperScanner 是mybatis自己的,所以在调用scan方法的时候,会调用到自己的scan方法
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
这里的扫描方法,其实和spring自己的扫描bean是几乎上一致的,只是在扫描出来包下对应的class文件之后,判断条件不一样,mybatis会判断class是否是接口等等
在扫描了之后,有一个很重要的操作:processBeanDefinitions()
修改beanDefinition的属性
我们需要考虑下,为什么在将接口扫描出来之后,要修改对应的beanDefinition属性?
首选我们来说processBeanDefinitions()方法中,做了什么修改
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");
}
// 1.添加一个构造函数
// 2.将beanDefinition的beanClass设置为mapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
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) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
// 3.将beanDefinition的自动注入属性设置为byType
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
这里我们可以认为有两个关键点:
1.将beanDefinition的beanClass设置为mapperFactoryBean
2.将beanDefinition的autowireMode设置为byType
我们考虑第一点,为什么将beanClass设置为mapperFactoryBean?
可以想下,一个接口,我们怎么将其注入到service?接口是没有业务逻辑的,所以只能是注入一个动态代理对象,所以这里的mapperFactoryBean是为了在会将mapper接口注入到service的时候,生成一个mapper接口的代理对象,注入到service,这里和mapperFactoryBean的具体逻辑有关系,不做深入说明
接着考虑第二点:为什么将自动注入模型设置为byType,需要注意,这里的byType和@Autowired的byType是完全不同的两个概念,我之前博客中有说明过,如果自动注入模型是byType,那我们在注入的时候,就不需要在属性上添加任何注解,只需要提供对应的set方法,并且set方法入参是我们要注入的类型即可
这也就是为什么要设置为byType的原因,我们想下,如果不设置为byType,那在mapper接口中注入sqlSessionFactory或者是sqlSessionTemplate的时候,还需要提供@Autowired注解,那这不就又和spring强耦合到一起了吗?所以将autoWireMode设置为byType,就不需要和spring强耦合
总结
所以,整个笔记写下来,我们也知道了,spring和mybatis在整合的时候,不仅仅用到了factoryBean、自动注入模型这个扩展点,在进行mapper接口扫描的时候,也用到了BeanDefinitionRegistryPostProcessor这个扩展机制