浅析Mybatis利用Spring扩展点之ImportBeanDefinitionRegistrar

看懂这篇文章可能需要有一定的spring源码基础,在分析原理之前,先简单讲一下应用。。。
使用spring boot结合mybatis开发时,会在pom.xml中引入下面依赖:

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.1</version>
</dependency>

而这个依赖实际上引入了以下依赖:

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
</dependencies>

可以看到实际上引入了mybatis-spring ,这个jar包是为了让mybatis与spring两个框架更好的集成、更方便开发人员而开发的。

使用mybatis时,常用的标记Mapper所在位置的两种方式为:
1.在每个Mapper(示例代码的Dao层)文件上加上@Mapper注解,如下:
在这里插入图片描述
2.在spring boot启动类上加上@MapperScan注解,并指定包名,如下:
在这里插入图片描述
鄙人的代码结构如下:
在这里插入图片描述
下面将按照这两种配置方式来分析mybatis是如何利用spring的扩展点的,首先分析 @Mapper方式,故事还得从spring初始化讲起。。。

1.在spring初始化时,会调用org.springframework.context.support.AbstractApplicationContext#refresh,方法中有这样一行代码:

// Invoke factory processors registered as beans in the context.
// 调用 BeanFactory 后置处理器
invokeBeanFactoryPostProcessors(beanFactory);

2.这行代码会一直调用到 org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry,方法中有这样一行代码:

processConfigBeanDefinitions(registry);

3.这行代码会一直调用到 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars,代码如下:

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
        // 这行代码的意思就是循环调用实现了ImportBeanDefinitionRegistrar的类的registerBeanDefinitions方法
		registrars.forEach((registrar, metadata) ->
				registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

可以看到registrars中有一个元素,如下:
在这里插入图片描述
4.MybatisAutoConfiguration的静态内部类AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,并重写了registerBeanDefinitions()方法,主要代码如下:

public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

	@Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	  
	  // 这句日志说明了这个方法的作用
      logger.debug("Searching for mappers annotated with @Mapper");

      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }

        // 拿到需要扫描的包名,默认是顶层包,鄙人的为:com.example.demo 
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        if (logger.isDebugEnabled()) {
          for (String pkg : packages) {
            logger.debug("Using auto-configuration base package '{}'", pkg);
          }
        }

		// 设置AnnotationClass: Mapper.class
        scanner.setAnnotationClass(Mapper.class);
        // 注册过滤器,会把包含了 Mapper.class 的过滤器注册进去
        scanner.registerFilters();
        // 扫描包
        scanner.doScan(StringUtils.toStringArray(packages));
      } catch (IllegalStateException ex) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
      }
    }
}

5.调用org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法来扫描类,代码如下:

	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
		    // 扫描类路径以查找候选组件
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

6.进入 findCandidateComponents(basePackage) 方法,这个方法是其父类ClassPathScanningCandidateComponentProvider.java 中的方法,方法中又调用了 scanCandidateComponents() 方法,如下:
在这里插入图片描述
先看上面那个 isCandidateComponent(metadataReader) ,如下:
在这里插入图片描述
只有这个方法返回true时才会继续执行第二个 isCandidateComponent(sbd) ,如下:
在这里插入图片描述
满足这两个条件才会将符合条件的类添加到candidates中,在示例代码中,最终有两个符合条件的类:MenuDao和StudentDao,如下:
在这里插入图片描述
7.完成Mapper(示例代码的Dao层类)的扫描后,回到org.mybatis.spring.mapper.ClassPathMapperScanner#doScan,继续完成BeanDefinition的处理,调用 processBeanDefinitions(beanDefinitions) ,这个方法中有一个很重要的操作:将自动注入模式设置为 byType,这个操作对使用@Autowired注解注入Mapper时有重大意义,如下:
在这里插入图片描述
完成这个操作后,按照spring初始化流程理解即可。

接下来分析第二种方式: @MapperScan(basePackages = “com.example.demo.dao”)
1.查看 @MapperScan 注解:
在这里插入图片描述
这个注解使用 @Import 引入了 MapperScannerRegistrar.class ,这个@Import 的作用可以简单理解为让 @MapperScan 拥有 MapperScannerRegistrar 的功能,所以重点看一下 MapperScannerRegistrar.class ,如下:
在这里插入图片描述
MapperScannerRegistrar 也实现了 ImportBeanDefinitionRegistrar ,并重写了registerBeanDefinitions()
2.看到 registerBeanDefinitions() 方法最后的代码:
在这里插入图片描述
这时候 basePackages = “com.example.demo.dao” ,然后也会调用org.mybatis.spring.mapper.ClassPathMapperScanner#doScan 方法来扫描,后续的流程基本上与第一种方式相同了。

通过两种方式对比发现:用 @MapperScan 来指定 Mapper 所在包名时,在扫描前得到的 basePackages 范围更小,所以扫描时效率会更高。

在使用mybatis时,如果结合了注解和xml两种方式来编写sql,需要在spring boot配置文件中指定xml的路径,如下:

mybatis:
  mapper-locations: classpath:mapping/*.xml

示例就是同时使用了两种方式,这种情况下,先处理Mapper.java,后处理Mapper.xml ,那么xml是什么时候处理的呢?
在调用org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory创建org.apache.ibatis.session.SqlSessionFactory对象时,会调用到org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory,在这个方法中会判断 mapperLocations 是否为空,对应上面那个配置,如果不为空则会循环解析xml,如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值