springboot整合mybatis源码分析

目的

上篇博客,我说了两种整合的方式,其中关键的一句话就是

 1. 在mapper接口(dao接口)中,使用@Mapper注解,这种方式,无需使用配置类,无需使用@MapperScan注解,即可整合
 2. 在mapper接口中,使用@Repository注解或者不添加任务注解,在全配置类上添加@MapperScan注解,并指定要扫描的包
这篇博客, 我说下这两种整合方式的原理

原理解析

在整合的时候,
如果使用的是@MapperScan注解这种方式,那么:会在该注解中,通过@Import注解引入MapperScannerRegistrar这个类,该类实现了ImportBeanDefinitionRegistrar,所以,会在spring扫描包的时候,扫描到该方法,并执行对应的registerBeanDefinitions方法,在该方法中,完成了对mapper接口的扫描;
如果是采用第二种方式,@Mapper注解,是由MybatisAutoConfiguration的一个内部类完成的

这两种方式,还有一个区别:第一种方式是spring-mybatis.jar包中的类完成的;第二种方式是mybatis-spring-boot-autoconfigure.jar包中完成的

什么是整合,我觉得就是一句话,把mybatis的mapper接口交给spring管理
下面分别是昨天说的第一种第二种方式对应的处理链

# 这是第一种方式:通过@MapperScan注解
org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions(org.springframework.core.annotation.AnnotationAttributes, org.springframework.beans.factory.support.BeanDefinitionRegistry)
	org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
		org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
			org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents


# 第二种方式  通过@Mapper注解
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar
	org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
		org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
			org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents

我们先说第二种方式:
这是MybatisAutoConfiguration的内部类,用来判断是否需要注入@Mapper注解、扫描mapper接口的处理类

下面这个内部类的其中一个注解是比较巧妙的
@Configuration
@Import({
    
    MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({
    
    MapperFactoryBean.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
    
    
    public MapperScannerRegistrarNotFoundConfiguration() {
    
    
    }
    public void afterPropertiesSet() {
    
    
        MybatisAutoConfiguration.logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
}

@ConditionalOnMissingBean({MapperFactoryBean.class})
这个注解的意思是:如果spring容器中有MapperFactoryBean,这个类就不会生效,相应的注解也不会生效(关于@Conditional注解我没有仔细查过资料,感觉应该是这个意思)
那什么时候,spring容器中会存在MapperFactoryvBean对象?很简单,在配置类加上一个@MapperScan注解即可,因为该注解,在对包下的class进行扫描的时候,会获取到当前包下所有的mapper接口(dao接口),并将mapper接口的beanDefinition对象的beanClass设置为MapperFactoryBean;

org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions方法中

definition.setBeanClass(this.mapperFactoryBean.getClass());

这里是如何扫描的,我们先不细说,后面我会单独写一篇博客来描述mybatis扫描的细节,这里我们只需要知道,如果加了@MapperScan注解,扫描到的mapper接口,在spring中的beanClass都是MapperFactoryBean对象;这里之所以将mapper接口的beanClass设置为MapperFactoryBean,是为了生成接口的代理对象,在service注入dao接口的时候,会将dao的代理对象注入到service中,代理对象就是在MapperFactoryBean的getObject()方法中生成的

这里我们就知道了,如果我们通过@MapperScan注解来扫描mapper接口的话,MybatisAutoConfiguration的内部类AutoConfiguredMapperScannerRegistrar就不会生效,也就不会去扫描

那为什么说,如果没有加@MapperScan注解,就一定要在mapper接口中使用@Mapper注解来修饰呢?因为在AutoConfiguredMapperScannerRegistrar中有这么一行代码

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
    if (!AutoConfigurationPackages.has(this.beanFactory)) {
    
    
        MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
    } else {
    
    
        MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
    
    
            Iterator var4 = packages.iterator();

            while(var4.hasNext()) {
    
    
                String pkg = (String)var4.next();
                MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
            }
        }

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        if (this.resourceLoader != null) {
    
    
            scanner.setResourceLoader(this.resourceLoader);
        }

				# 这里这行代码
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(packages));
    }
}

上面这行代码会把mapper.class设置到annotationClass中,然后再sacnner.registerFilters()方法中,将annotationClass添加到了includeFilters中
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
关于includeFilters和excludeFilters,在前面的博客中有提到过扫描bean的原理,大体的意思就是:
spring在将包下所有的class扫描出来之后,需要进行过滤,并不是所有的class都要注入到spring容器中的,在过滤的时候,有两层过滤,
第一层:根据includeFilters和excludeFilters进行过滤,符合includeFilters的,就是可以注入的,符合excludeFilters的,就是无需注入的
第二层:由于spring和mybatis分别是扫描类和接口,所以,是通过不同的条件来判断的

一、spring是调用的org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.beans.factory.annotation.AnnotatedBeanDefinition)

		protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    
    
			AnnotationMetadata metadata = beanDefinition.getMetadata();
			/**
			 * spring在扫描普通bean,是用的这个方法,mybatis对该方法进行了扩展,mybatis判断一个beanDefinition是否要放到beanDefinitionMap中,是判断当前是否是接口,是否是顶级类(org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent)
			 *
			 * isIndependent:当前类是否独立(顶级类或者嵌套类)
			 * isConcrete: 不是接口,不是抽象类,就返回true
			 * 如果bean是抽象类,且添加了@LookUp注解,也可以注入
			 * 使用抽象类+@LookUp注解,可以解决单实例bean依赖原型bean的问题,这里在spring官方文档中应该也有说明
			 */
			return (metadata.isIndependent() && (metadata.isConcrete() ||
					(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
		}


		ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner
		ClassPathBeanDefinitionScanner继承了ClassPathScanningCandidateComponentProvider

二、mybatis是通过org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent
		@Override
		protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    
    
		  // 判断beanDefinition是否是接口,或者是否是顶级类
		  return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
		}

这里ClassPathMapperScanner的继承关系,也就是说,在扫描bean的时候,mybatis自己的ClassPathMapperScanner继承了spring的ClassPathMapperScanner
在这里插入图片描述
所以,总结一下:
如果我们不使用MapperScan注解的时候,必须要用@Mapper注解修饰mapper接口,因为不使用@MapperScan注解的话,是由AutoConfiguredMapperScannerRegistrar完成扫描的,该类在扫描的时候,指定了includeFilters,是Mapper.class;如果我们的mapper接口没有用@Mapper注解,那么在扫描到包下所有的class之后,第一层过滤的时候,就会过滤掉

如果我们使用的是@MapperScan注解,那就无需添加@Mapper注解,因为@MapperScan注解,会通过import注解引入MapperScannerRegistrar,然后在MapperScannerRegistrar类中,会对接口进行扫描,扫描的时候,并没有在includeFilters中指定必须要用@Mapper注解;所以可以不添加@Mapper注解

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/107536970