上一篇文章,讲到源码的分析,这次来分析mybatis与spring进行集成的源码。
spring里的一些基本概念,要做一个简单的介绍,否则深入到spring与mybatis集成的源码是比较难理解。
Spring的类图轮廓,其中BeanFactory是容器的最抽象接口,下面一些接口扩展了接口抽象,继承的层次越多,一般接口的能力就越强大,设计这样的层次接口,但是真正实现类会聚合在几个类。
所以为什么要设计这样复杂的接口层次呢,写一个大接口,去实现不就可以了吗,面向对象设计原则里有一个准则就是接口隔离,和最小职责。对于每个容器的试用方来说,耦合尽量少的接口信息,所以往往会声明为更抽象的接口,这样降低耦合度。另一方面接口的层次结构,可以给后面的扩展提供空间,只是现在spring的默认容器实现了比较丰富的很多接口,可能其他jar通过SPI实现了其他类型的容器,这就给其他jar提供了选择的权利。
整个执行链路主要就是在扫描配置,加载类注册为bean到一个容器,并在内存构建一个对象关系,依赖注入就是通过大的工厂容器注入,控制权的反转就是把对象生成的控制权由开发者反转给容器去处理。图上描述了AbstractApplicationContext整个容器启动的的SOP,其中有一大特点就是对于扩展点的开放,我们可以在整个容器的声明周期去做很多干预,例如bean的注册,属性的初始化,初始化的前后等等。spring提供了类似于观察者模式的方式处理。这里介绍几个此次分析需要的几个重要扩展接口:
BeanFactoryPostProcessor是容器在初始化beanDefinition但是还没实力话的时候扩展接口。
FactoryBean 是一个特殊的bean,spring在处理实现这个接口的bean的时候,并不会直接把它注册为一个bean,而是会调用其getObject()方法返回的对象作为bean管理。
BeanDefinition 这是Spring对于所有Bean的一个封装,因为在Spring看来任何需要被容器管理的对象都需要遵从一套标准的管理规则,例如其是否单例,其在容器中的是否单例,是否懒加载等等,所以Spring容器启动的第一步就是把所有识别所有需要被管理的类,并进行封装成BeanDefinition管理。
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会把所有选择器(实现)
这里会把实现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加载的方式。