SpringBoot自动化配置mybatis源码分析

上一篇文章,讲到源码的分析,这次来分析mybatis与spring进行集成的源码。

Spring骨架接口定义

spring里的一些基本概念,要做一个简单的介绍,否则深入到spring与mybatis集成的源码是比较难理解。
在这里插入图片描述
Spring的类图轮廓,其中BeanFactory是容器的最抽象接口,下面一些接口扩展了接口抽象,继承的层次越多,一般接口的能力就越强大,设计这样的层次接口,但是真正实现类会聚合在几个类。
所以为什么要设计这样复杂的接口层次呢,写一个大接口,去实现不就可以了吗,面向对象设计原则里有一个准则就是接口隔离,和最小职责。对于每个容器的试用方来说,耦合尽量少的接口信息,所以往往会声明为更抽象的接口,这样降低耦合度。另一方面接口的层次结构,可以给后面的扩展提供空间,只是现在spring的默认容器实现了比较丰富的很多接口,可能其他jar通过SPI实现了其他类型的容器,这就给其他jar提供了选择的权利。
在这里插入图片描述

Spring执行与重要扩展

在这里插入图片描述
整个执行链路主要就是在扫描配置,加载类注册为bean到一个容器,并在内存构建一个对象关系,依赖注入就是通过大的工厂容器注入,控制权的反转就是把对象生成的控制权由开发者反转给容器去处理。图上描述了AbstractApplicationContext整个容器启动的的SOP,其中有一大特点就是对于扩展点的开放,我们可以在整个容器的声明周期去做很多干预,例如bean的注册,属性的初始化,初始化的前后等等。spring提供了类似于观察者模式的方式处理。这里介绍几个此次分析需要的几个重要扩展接口:
BeanFactoryPostProcessor是容器在初始化beanDefinition但是还没实例化的时候扩展接口。
FactoryBean 是一个特殊的bean,spring在处理实现这个接口的bean的时候,并不会直接把它注册为一个bean,而是会调用其getObject()方法返回的对象作为bean管理。
BeanDefinition 这是Spring对于所有Bean的一个封装,因为在Spring看来任何需要被容器管理的对象都需要遵从一套标准的管理规则,例如其是否单例,其在容器中的是否单例,是否懒加载等等,所以Spring容器启动的第一步就是把所有识别所有需要被管理的类,并进行封装成BeanDefinition管理。

相关jar

Spring boot环境下,我们只要依赖一堆jar即可完成对很多功能的集成,同样mybatis也是这样被集成进来,其主要涉及到这样四个包。
在这里插入图片描述
其中Spring主要包括了spring核心的几个包和spring boot的包,这里不展开。
mybatis-spring-boot-autoconfigure与mybatis-spring是完成mybatis与spring集成的模块,后者主要完成了前者的一个自动装配。

源码分析

在spring boot的环境下应用启动会通过SpringApplication这个类的run方法启动spring boot容器。
SpringApplication.run主要做了是那么事呢?
在这里插入图片描述
我们进入创建容器的方法,内部会根据当前环境选择对应默认的上下文实现。如果没有集成servlet容器就会使用AnnotationConfigApplicationContext进行初始化。其支持注册配置的容器上下文,其构造方法如下:
在这里插入图片描述
这是Spring-context的类,其是在spring3.0才支持的注解方式类扫描定义注册上下文。AnnotationConfigApplicationContext同时也间接实现了BeanDefinitionRegistry,也就是说其可以去注册bean,它把自己当作注册器传递给注册器AnnotatedBeanDefinitionReader。在这里插入图片描述
registerAnnotationConfigProcessors这里完成了默认BeanFactoryPostProcessor。
在这里插入图片描述
这里会注册一个扩展点ConfigurationClassPostProcessor,其就是用来在容器启动中去扩展处理配置bean的,我们再回顾一下它会在哪里进行切入。
在这里插入图片描述
也就是整个spring容器进行刷新的时候扩展。
在这里插入图片描述
并且其先后顺序先处理registry注册处理器,再处理容器处理器,简单来说先调用postProcessBeanDefinitionRegistry, 再调用postProcessBeanFactory:
在这里插入图片描述
所以我们先分析postProcessBeanDefinitionRegistry
在这里插入图片描述
processConfigBeanDefinitions实现比较长,我们挑我们关注点,不要陷入到其他实现细节去。其中关键代码

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		// 此处遍历所有已经注册的bean
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if 
			(ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			// checkConfigurationClassCandidate递归判断了,bean的注解是否包含@Configuration注解,如果是就添加到configCandidates,也就是把它识别为一个配置bean的一个配置类,后面会再次处理这个bean的注解,解析它是否还定义了其他的bean的加载,bean的注册,例如@ComponentScan和@Import的方式
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
		
		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
		do {
			parser.parse(candidates);
			parser.validate();
			// ...
		}
		while (!candidates.isEmpty());
		// ...
		}
	}

这里回到SpringBoot的启动类的注解@SpringBootApplication,其定义在这里插入图片描述
因此会被识别为一个候选配置根类。
@EnableAutoConfiguration的定义:
在这里插入图片描述
其有个Import,导入了一个选择器EnableAutoConfigurationImportSelector。

我们继续回到processConfigBeanDefinitions方法,解析到配置根类之后
在这里插入图片描述
对其进行parse操作,一步步进入调用栈,会到doProcessConfigurationClass方法,在这里插入图片描述
这里就开始处理刚才配置根类的扩展导入BeanDefinition,doProcessConfigurationClass内部实现了一个递归扫描的结构processImports,大概做的事情就是根据这个根配置类去遍历所有注解上需要导入的bean,对于SpringBoot的@SpringBootApplication有EnableAutoConfigurationImportSelector。
在这里插入图片描述
processImports会把所有选择器(实现)
processImports处理完了所有Import注解扩展选择器之后赋值给deferredImportSelectors。再在processDeferredImportSelectors进行统一处理。
这里会把实现ImpoertSelector的接口select赋值给deferredImportSelectors,在processDeferredImportSelectors统一去处理

	private void processDeferredImportSelectors() {
		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
		this.deferredImportSelectors = null;
		Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

		for (DeferredImportSelectorHolder deferredImport : deferredImports) {
			ConfigurationClass configClass = deferredImport.getConfigurationClass();
			try {
			// 遍历selector进行导入
				String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
				processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
		}
	}

在这里插入图片描述
在这里插入图片描述
SpringFactoriesLoader.loadFactoryNames的实现就是去指定目录加载Factory
在这里插入图片描述
在这里插入图片描述
这里加载jar的spring.factories文件,我们看下这个文件是啥
在这里插入图片描述
只要是xxx-autoconfigure都有这样一个配置文件,再来看这个方法的调用
在这里插入图片描述
我们看mybatis的自动配置的包:
在这里插入图片描述
这就是提供了SPI机制去加载,SpringFactoriesLoader是spring-core的包的类,可以主动去加载所有jar的spring.factories进行初始化。mybatis的spring.factories里配置了MybatisAutoConfiguration,就能被加载到,其内部初始化了SqlSessionFactory与Mapper接口的扫描

结论

所以springboot对于mybatis的集成主要通过Spring.factories,包括其他的自动化配置包,都是一个套路,根据源码的分析,我们主要学习了,进行bean注册的方式,不仅有xml、@Componet,还有@ComponentScan,实现ImportSelector或则实现ImportBeanDefinitionRegistrar。以后我们可以更加灵活的应用SpringBoot的这种扩展去控制bean加载的方式。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值