一、MapperScan使用
MapperScan注解里很详细的给出了MapperScan注解的使用方法;MapperScan极大简化了mybatis的使用成本,不用在每个Java接口里配置@Mapper接口,同时也不用在使用SqlSessionTemplate.getMapper(PersonDao.class)去获取代理对象了,而是直接可以通过在使用的地方通过@Autowired获取了,
我们知道@Autowired,是从spring容器里获取对象,且获取到的对象肯定是代理对象,因为我们给出的是接口地址,那mybatis-spring是如果把代理对象注册到spring的呢?
首先@MapperScan注解是怎么被解析的呢?
我们看import的MapperScannerRegistrar.class,实现了ImportBeanDefinitionRegistrar接口,所以spring启动的时候会调用到这个方法:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
//读取mapperScan注解配置的路劲,然后使用ClassPathMapperScanner开始扫面
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(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()));
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
ClassPathMapperScanner.java的doScan(StringUtils.toStringArray(basePackages));
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//获取到Java类的beanDefines
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;
}
Set beanDefinitions = super.doScan(basePackages);会获取到Java类的beanDefine,但是我们这里的Java类是我们声明的接口,并不是可以实例化的类,所以spring是无法实例化成bean的,那么需要怎么做呢?接着往下看 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");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
//这里是重点,把beanDefine的Class换了mapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());(1)
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;
}
。。。省略部分代码
}
我们看到在(1)这里把BeanClass替换成了mapperFactoryBean,而这个类是啥样的呢?
这个类在scanner创建的时候被创建;
(1)注释里介绍了单独使用这个类的用法,也就是手动获取一个接口的代理对象Mapper的方法;
(2)看这个类的原理,这个类继承了FactoryBean,所以spring实例话它的时候肯定会调用getObject()方法,
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
到这里之后,如果看过我的上一篇博客mybatis初始化的就一切都清晰了,后面的获取代理对象是mybatis内部实现的;会去获取mybatis的Configuration里代理对象;
二、MapperScannerConfigurer的原理和@MapperScan原理是一样的:
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,所以spring会调用到
@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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
里面也是使用了ClassPathMapperScanner。