一、@Mapper注解
在springboot+mybatis的工程中,如果不做特殊配置,mybatis会查找有@Mapper
的接口创建其代理对象添加到spring容器中,接下来就来分析下这个是如何实现的。
关键点就在MybatisAutoConfiguration这个自动配置类中
public class MybatisAutoConfiguration {
//这个配置会在没有进行其他dao接口扫描的配置时生效,重点在导入的AutoConfiguredMapperScannerRegistrar
//它是这个类中的一个内部类
@Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
@PostConstruct
public void afterPropertiesSet() {
log.debug(String.format("No %s found.", MapperFactoryBean.class.getName()));
}
}
//使用默认配置时,把这个类加入spring容器中,它会自动查找有Mapper注解的接口
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
log.debug("Searching for mappers annotated with @Mapper'");
//这个scanner是mybatis继承spring的ClassPathBeanDefinitionScanner后实现的扫描
//dao接口的扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
List<String> pkgs = AutoConfigurationPackages.get(this.beanFactory);
for (String pkg : pkgs) {
log.debug("Using auto-configuration base package '" + pkg + "'");
}
//这里指定扫描Mapper注解
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(pkgs));
} catch (IllegalStateException ex) {
log.debug("Could not determine auto-configuration " + "package, automatic mapper scanning disabled.");
}
}
}
可以看到是通过ClassPathMapperScanner来扫描有Mapper注解的接口,具体的扫描原理后续会介绍
二、@MapperScan注解
在springboot工程中也可以通过MapperScan注解来指定dao接口的扫描路径,我们来分析下这个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
...省略其他
}
这个注解上导入了一个MapperScannerRegistrar类,此类用来根据配置的注解属性进行扫描。
接下来分析下这个类
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
//这个方法是具体的扫描逻辑,总的来说就是根据配置的MapperScan注解属性,使用
//ClassPathMapperScanner注解来扫描classpath路径下满足条件的接口创建代理对象
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//创建扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
//设置扫描器的扫描条件
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<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
//执行sql
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
三、ClassPathMapperScanner
接下来分析下ClassPathMapperScanner的源码,它继承自spring的ClassPathBeanDefinitionScanner
,spring这个scanner扫描时会跳过接口,mybatis继承这个scanner并扩展了它的扫描逻辑
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//调用spring的扫描逻辑先把所有符合条件的class都扫描出来
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定义信息,用MapperFactoryBean来替换原来的dao接口
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
//循环bean定义信息
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// definition.getBeanClassName()获取到的是dao接口的class,底下这句是把dao接口的class对象
// 作为了创建这个bean时构造方法的参数,后续会把bean定义的class替换成MapperFactoryBean,
// 所以也就是创建MapperFactoryBean对象时会传递dao接口的class作为构造方法参数
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//替换bean定义的class为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
//以下这些都是在设置bean定义的一些属性
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() + "'.");
}
//设置这个bean中属性的注入模式为根据类型自动注入,这样当创建这个bean时就会自动给其中的属性
//赋值
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
}
所以总的来讲,ClassPathMapperScanner就是把spring扫描到的bean定义信息中的class替换成了MapperFactoryBean
,真正的dao接口代理对象是由它创建的。而它必然实现了spring的FactoryBean
接口
四、 MapperFactoryBean
dao接口的代理对象最终是由这个类创建出来的,它实现了spring的FactoryBean
接口,在其getObject方法中会调用
sqlsession来创建出代理对象
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
//构造方法传进来的参数是dao接口的class对象,最终会创建它的代理对象
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
// 通过sqlsession来创建代理对象,sqlsession是父类中的属性
return getSqlSession().getMapper(this.mapperInterface);
}
}