Spring Boot 自动装配原理 / 3
ShawJie

Spring Boot 自动装配原理 / 3

在上一篇内容中,我们了解了关于Spring Boot在初始化过程和启动准备过程中,主类被加载进Bean容器的过程,以及spring.factories文件的加载过程及内容,还没有看的同学可以去看一看,链接在这里。本节将会针对@EnableAutoConfiguration的解析过程,以及spring.factories内自动装配相关类是如何进行过滤的,本篇由于堆栈过程较多,整体节奏会比较快,也建议大家能打开编译器下载好源码按照文字中的顺序进行阅读。

主类的注解解析过程

​ 在上一节中,我们的Spring Boot应用已经顺利完成了初始化阶段、准备阶段的工作内容,正式进入了启动阶段。接下来就需要面对本系列内容面对的核心问题:”Spring是在何时进行自动装配注解的解析的,以及自动装配类是再何时、如何进行加载的。”

​ 我们都知道,Spring的一大优势是对项目内的对象进行统一的生命周期管理以达成控制反转的目的,而BeanFactory则负责了整体的对象管理的工作。在第一篇内容中我们提到在Spring应用启动阶段,即ApplicationContext.refresh()方法内,会调用BeanFactory的后处理器,以执行Spring Bean的解析、注册操作。

​ 因为Spring的Bean注册解析堆栈太深,关于BeanFactory的后置处理工作就不于此贴代码了,想具体了解的同学可以以AbscractApplicationContext#refresh() -> invokeBeanFactoryPostProcessors() -> PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors() -> invokeBeanDefinitionRegistryPostProcessors()为主要调用链路进行代码的阅读,在文章内就只拣重点的部分来看了。

配置类的后置处理器

​ 在org.springframework.context.support.PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors()方法中,BeanFactory会通过getBeanNamesForType获取BeanDefinitionRegistryPostProcessor实现类。

1
2
3
4
5
6
7
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}

​ 于此处我们会获得一个实现类ConfigurationClassPostProcessor,该类会负责解析注解了@Configuration的Bean。那么这个类的来源是在哪呢?这里我们就要回头再去看看Spring应用再初始化阶段推断、构建上下文的过程。在通过应用类型构建对应的Spring上下文之后会通过类全限定名获取类信息,而后通过反射进行类构建,但无论是Servlet应用还是Reactive应用,抑或是非web应用,其上下文类构造方法里我们都能看见此段代码this.reader = new AnnotatedBeanDefinitionReader(this),在AnnotatedBeanDefinitionReader的构造函数中,我们能看到该类会向BeanFactory中注册一些相关的后置处理器,其中就包括了ConfigurationClassPostProcessor

1
2
3
4
5
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}

​ 在我们明确了具体的具体的后置处理器之后,就可以跟进到ConfigurationClassPostProcessor#processConfigBeanDefinitions()方法内部去看进一步的解析情况了。

ConfigurationClassParser

​ 在processConfigBeanDefinitions()方法的内部逻比较明确,我会在下面贴出部分代码,省略掉中间一些并不是太重要的内容并用注释描述部分逻辑做用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
/**
* 获取到BeanFactory中所有的Bean信息的名称
* 在之前的内容中已经学习了主类的加载注册过程,此时理所当然的主类信息也在其中
*/
String[] candidateNames = registry.getBeanDefinitionNames();

// 遍历bean名称
for (String beanName : candidateNames) {
// 通过名称获取bean定义信息
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// 通过获取bean定义的配置属性用于判断该类是否已经被处理过,若处理过则跳过
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
/**
* checkConfigurationClassCandidate方法会对bean定义信息进行判断
* 若该类信息持有@Configuration的定义,则设置其的配置属性并返回true
*/
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// 向configCandidates插入配置类的类定义
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 非主要逻辑代码,我们暂时可以不必关心
...

// 构建一个配置类的解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 将前面筛选出来配置类定义放进candidates链表中
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 对配置类信息进行解析
parser.parse(candidates);
}while (!candidates.isEmpty());
}

​ 进入到类的解析过程,已经里我们的目标越来越近了。该过程将会解析类的@PropertySource@ComponentScan@Import@ImportResource@Bean几个注解。在第一篇内容中我们提到了@EnableAutoConfiguration的元注解@Import(AutoConfigurationImportSelector.class)在这终于派上了用场。我们暂且忽略其它的注解处理逻辑,只关注相关的@Import部分。

1
2
3
4
5
6
7
/**
* ConfigurationClassParser#processImports方法用于处理类的@import注解信息
* 通过getImports方法可以通过类源信息获取到类的直接注解以及其元注解@import相关的类
* 即在此处可以通过getImorts方法获取到主类的@EnableAutoConfiruation的元注解
* @import的值 AutoConfigurationImportSelector.class
*/
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

​ 在procressImports()方法内部,我们也只需要关注其中一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
for (SourceClass candidate : importCandidates) {
/**
* 判断类是否是ImportSelector的子类,这方面我们可以具体看一看
* AutoConfigurationImportSelector类的实现继承关系
*/
if (candidate.isAssignable(ImportSelector.class)) {
// 确保类已经加载
Class<?> candidateClass = candidate.loadClass();
// 进行类实例化工作
ImportSelector selector =
ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
// 获取需要排除的类信息
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
/**
* 由于AutoConfigurationImportSelector实现于DeferredImportSelector
* 所以会在初次解析时先加入延迟处理器中,等解析逻辑完成之后再由解析器进行逻辑调用
*/
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
...
}
// 其它非主要相关逻辑
...
}

​ 在解析过程结束后,解析器就会通过this.deferredImportSelectorHandler.process(),进行解析过程中设定的延迟处理操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void process() {
// 获取到延迟执行的ImportSelector
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
// 清空延迟执行列表,保证不会被多次执行
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler =
new DeferredImportSelectorGroupingHandler();
// 根据Order对ImportSelector进行排序
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 通过Register对ImprotSelector根据类型进行分组,并构建封装成Grouping对象
deferredImports.forEach(handler::register);
// 实际调用ImportSelector的处理操作
handler.processGroupImports();
}
}
finally {
// 初始化一个集合,保证之后不会出现空指针异常
this.deferredImportSelectors = new ArrayList<>();
}
}

自动装配的处理过程

​ 跟进handler#processGroupImports()方法,在该方法内部,会通过调用ImprotSelecotprocess()方法获取需要导入的定义列表。因为@EnableAutoConfiguration导入的类是AutoConfigurationImportSelector,我们可以从此类的AutoConfigurationGroup#procress()方法作为起始点进行分析。

1
2
3
4
5
6
7
8
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}

spring-autoconfigure-metadata.properties

​ 在process()方法内主要有两个要点需要注意,第一部分是getAutoConfigurationMetadata()方法,该方法会通过类加载器扫描类路径下的META-INF/spring-autoconfigure-metadata.properties,这个文件记录了关于自动装配类的一些元信息,我们可以先看一下这个文件的内容结构,具体的做用过会儿我们再说。

1
2
3
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration.ConditionalOnWebApplication=SERVLET
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration.ConditionalOnBean=javax.jms.ConnectionFactory

​ 文件内容结构可以总结为:[类全限定名].[条件]=[条件值]

筛选自动装配类列表

process()方法内的第二个要点就是getAutoConfigurationEntry()方法的内部逻辑,该部分也是Spring Boot自动装配的核心之所在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 判断是否允许自动装配 若不允许则直接返回空集 默认允许
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取@EnableAutoConfiguration注解的参数列表
AnnotationAttributes attributes = getAttributes(annotationMetadata);
/**
* 从spring.factories文件(缓存)中获取EnableAutoConfiguration相关的配置类
* 具体的获取方法在第二篇内容已经做过分析 这里不再赘述
*/
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 根据注解参数里的exclude配置进行类排除
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 进行类定义的过滤操作
configurations = filter(configurations, autoConfigurationMetadata);
// 通知监听器
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

​ 除了从spring.factories获取到自动配置类列表之外,对类定义进行过滤操作也是十分重要的一环,我们需要排除掉自动配置条件不成立的自动配置类,只处理条件成立的那些自动装配类。这部分也是由于堆栈过多于此不再详细描述,就把重点的部分拎出来说一说。

filter()方法的内部逻辑是会从spring.factories文件内容(缓存)中获取到AutoConfigurationImportFilter相关的过滤类,默认的会有OnBeanConditionOnClassConditionOnWebApplicationCondition三种过滤器(可在spring-boot-autoconfigure-2.2.5.RELEASE.jar!META-INF/spring.factories找到这三个类的配置信息),当然我们也可以根据这种规则添加自己的过滤器(tip: 可在自定义过滤器中排除掉一些项目不需要的自动装配类,以减少自动装配类的解析时间,也是一种提升Spring Boot应用启动速度的小技巧)。通过spring.factories里获取到的类全限定名,拼接过滤方式,以在元信息列表里获取到过滤条件,而过滤器则会按照各自的规则进行判断。

  • 过滤方式
    • OnClassCondition
      • 获取到过滤条件,即需要存在的类全限定名之后。通过类加载器对类进行解析,若解析失败,抛出类不存在异常,则类不存在,反之则存在。
    • OnWebApplicationCondition
      • 获取到过滤条件,即需要对应的应用类型之后。通过对应用类型名称作对比,以及类类型所对应关键类的加载进行验证。若两者比对通过,则条件成立,反之则不成立
    • OnBeanCondition
      • OnBeanCondition的条件判定方式与onClassCondition类似,也是通过类加载器对类进行解析,若需求的类全部都存在则条件成立,反之则不成立。(这一块会留下一个疑惑,为什么判断的是类是否在类加载器中存在而不是在BeanFactory中存在呢?)

通过以上三种过滤方式对自动装配类列表进行第一次过滤,我们将spring.factories中大部分不需要的自动装配类已经被过滤掉了。之后就需要对类进行细粒度的解析,在processGroupImports()方法中,我们可以看到这么一行代码:

1
2
3
4
5
processImports(configurationClass, 
asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(
asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);

最终迎来的自动装配

procressImports()方法,是不是很熟悉。在上边我们提到的控制类解析过程中出现过它,Spring会对经过第一次筛选的配置类进行二次条件验证,即通过封装过的类定义获取到标注的条件信息,譬如@ConditionalOnMissingBean@ConditionalOnProperty以及其它一些自定义条件注解,关于@Conditional这方面的内容可能会在之后用一篇构建如何一个自定义Starter的过程中进行分析。在所有条件均判定通过后,shouldSkip()方法会返回false,之后则会进入配置类的解析过程,其中就包括@ComponentScan的扫描,@Bean的注册解析等等,解析的内容前面也提到了,于此就不再赘述了,而我们的Spring Boot 自动装配过程,也与此完成了它的全部流程。

小结

​ 从第一篇开始到现在,总算是把Spring Boot的自动装配流程梳理清楚,于此我再大致用链的方式梳理一遍。

​ 注解于主类之上的@SpringBootApplication -> 将主类注册进容器 -> 将spring.factories写入缓存 -> 配置类的注解解析 -> @Import注解的注入逻辑 -> 从spring.factories获取自动装配类列表 -> 进行第一次粗粒度过滤 -> 进行第二次精确过滤 -> 解析自动装配类实现自动装配过程。

​ 至此关于自动装配的过程就已经结束了,关于这这部分的内容大致我自觉已经讲清楚了,但是关于装配过程中的@Conditional注解判断可能之后还会拎出来一篇自动装配原理Plus配合构建自定义Starter进行一个收尾,既然的之后的事情那就之后再说把。关于内容的问题或者说有什么建议也欢迎直接联系我,我的邮箱是:Loverconcerto@outlook.com,个人微信可以搜索:Qt1491717547。总之,生活还要继续。加油。