SpringBoot BeanDefinition 加载过程

为什么要强调SpringBoot中的BeanDefinition加载过程?

在阅读很多相对比较旧的讲解Spring容器的书籍或文章时,由于当时SpringBoot并不是很盛行,甚至还没有SpringBoot,导致对于Spring容器启动的讲解并没有提到与SpringBoot容器启动过程的差异,导致很多读者默认为这两者是一样的。

事实上,在容器启动时,传统的配置式的Spring项目与使用了SpringBoot的项目在启动时大体上是相近的,但是区别也还是不小的,本文要提到的BeanDefinition 加载过程中是其中一个例子。

找不同

因为讲解Spring启动过程的文章已经很多了,在这里不详细介绍这部分内容,但是为了让读者阅读更清晰,以及便于理解,我们从源码中寻找一下两种方式差异的原因。

这里假设读者已经自行阅读过Spring启动的相关文章了,所以我们知道,Spring启动的核心过程是ApplicationContext#refresh方法。而加载BeanDefinition是在obtainFreshBeanFactory方法中,贴一下代码

1
2
3
4
5
6
7
8
9
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 刷新BeanFactory
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

这里调用了refreshBeanFactory方法进行刷新容器,想进一步查询其源码时,发现这个方法有两个实现类,分别是AbstractRefreshableApplicationContextGenericApplicationContext

代码到了这里出现了分岔路口,我们看看具体应该进入哪个分支。先回想一下传统方式创建一个Spring容器的方式

1
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("/src/main/resources/spring.xml");

看一下FileSystemXmlApplicationContext类的类继承图

FileSystemXmlApplicationContext的类继承图

可以发现FileSystemXmlApplicationContext继承AbstractRefreshableApplicationContext从而实现了refreshBeanFactory方法。我们进入AbstractRefreshableApplicationContext#refreshBeanFactory可以看到,跟我们之前了解的一样,在这里加载了BeanDefinition,简单贴一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected final void refreshBeanFactory() throws BeansException {
//如果原来已经存在了beanFactory,那么先销毁掉
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 手动创建一个DefaultListableBeanFactory
// 当前类也是BeanFactory的子类,但是实际实现都是委托给这个beanFactory来处理的
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//设置beanFactory的两个参数————是否允许Bean覆盖和是否允许循环依赖
customizeBeanFactory(beanFactory);
//加载BeanDefinition,由子类实现,有不同的加载方式,如从XML中、注解配置中获取等等
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

那么SpringBoot是如何呢?我们从代码中寻找答案。因为SpringBoot没有显示创建ApplicationContext的代码,我们只能从入口寻找。

一般情况下,我们是这么启动SpringBoot项目的:

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}

SpringBoot再神通广大,终归是用Java实现的,这个main方法我们再熟悉不过了,里面只调用了一个方法,即SpringApplication#run,那么秘密一定在里面。

run方法里面调用了自己的重载方法,然后又调用了另一个重载方法,直到

1
2
3
4
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}

我们继续进入其run方法看一下

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
43
44
45
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

这个方法代码很长,但我们的目的是找到ApplicationContext的实现类,所以我们不关心其他逻辑,在这里寻找ApplicationContext

很容易在第四行就找到ConfigurableApplicationContext类型的声明,可惜这里只有接口,没有实现类,但是通过context变量,我们可以很容易找到赋值的代码,第16行。从调用的方法名也可以确认,就是在createApplicationContext中创建了上下文,我们看一下其实现。

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
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 根据当前的应用类型,获取正确的类名
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
// 实例化对象
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

不出所料,在createApplicationContext方法中判断了应用环境选择了对应的上下文类型,并且将其实例化,也可以很容易发现,这里三个分支所对应的三个类,均继承自GenericApplicationContext

到这里,我们终于在代码中找到两种方式启动的差异点,我们看一下GenericApplicationContext#refreshBeanFactory方法的实现

1
2
3
4
5
6
7
protected final void refreshBeanFactory() throws IllegalStateException {
if (!this.refreshed.compareAndSet(false, true)) {
throw new IllegalStateException(
"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
}
this.beanFactory.setSerializationId(getId());
}

可以说是几乎什么也没做,只设置了一个序列化id。完全找不到之前看的那些加载BeanDefinition的代码。至此可以确定,SpringBoot项目的加载BeanDefinition过程并不在此处。

SpringBoot 加载 BeanDefinition的入口

那么SpringBoot具体是通过什么方式方式加载项目中的所有BeanDefinition的呢?

其实SpringBoot中,是通过@Configuration注解来作为所有配置的入口标记的,例如上文中的例子,Application类被添加了@SpringBootApplication注解,而@SpringBootApplication有被@SpringBootConfiguration注解标记,而@SpringBootConfiguration正是@Configuration的子类。

那么具体是在哪里提供了扫描@Configuration注解并加载所有配置的功能,在这里就不卖关子了,直接介绍本文的主角 ConfigurationClassPostProcessor

ConfigurationClassPostProcessor

我们先简单看一下这个类的继承关系,发现它继承自BeanDefinitionRegistryPostProcessor,关于这个类在Spring支持的扩展接口一文中介绍过,其中介绍postProcessBeanDefinitionRegistry方法内容如下:

该方法在所有BeanDefinition被加载完成后,BeanFactoryPostProcessor#postProcessBeanFactory之前被调用。

可以在此处修改已加载的BeanDefinition,或添加自定义的BeanDefinition,来实现动态注册Bean;也可以在此方法中注册其他BeanDefinitionRegistryPostProcessor,但如果当前Bean也是被其他BeanDefinitionRegistryPostProcessor#postProcessBeanFactory加载的,那么当前Bean加载的BeanDefinitionRegistryPostProcessor将没有机会被执行。

这个方法功能正好符合我们对ConfigurationClassPostProcessor这个类功能的期待,那么接下来看ConfigurationClassPostProcessor#postProcessBeanFactory的实现是否符合预期。

还是按照惯例贴上包括简单注释的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 为当前的BeanFactory生成一个id,防止同一个BeanFactory被重复处理
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
// 处理配置的BeanDefinitions
processConfigBeanDefinitions(registry);
}

发现这个方法中没有太多秘密,只是做了一个简单的检查,最终调用了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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
String[] candidateNames = registry.getBeanDefinitionNames();
// 遍历当前容器中所有的BeanDefinition,从中寻找有@Configuration的类
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);
}
}
// 判断是否有@Configuration注解,如果有就加入候选列表
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}

// 如果候选列表为空,就直接返回了
if (configCandidates.isEmpty()) {
return;
}

// 如果使用了@Order注解,就排个序
Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
@Override
public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
});

// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}

// 下面开始解析候选列表
// 创建一个ConfigurationClassParser,后面的解析将委托给这个对象
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();

Set<ConfigurationClass> configClasses =
new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// 如果BeanDefinitionReader为空,则创建一个,BeanDefinitionReader是用来加载BeanDefinition
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 前面ConfigurationClassParser#parse处理时遗留了三类类型,只将其解析并保存,并没有注册成BeanDefinition,在这个方法中进行处理
// 使用BeanDefinitionReader进行加载BeanDefinition,细讲
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);

candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
// 如果发现当前容器中的BeanDefinition数量比上一轮解析完以后的数量多,说明这一轮解析了新的BeanDefinition
// 这种情况需要对BeanDefinition列表逐个判断,如果其类型加了@Configuration注解,那么需要放入候选列表中,进行下一轮解析
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<String>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());

// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null) {
if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
}

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}

读者可以先通过代码外加笔者加的的中文注释对这个方法有个大致的理解,其实可以很容易发现这个方法正是用来处理@Configuration注解的。

简单总结一下这个方法中的流程:

  1. 从当前已注册的BeanDefinition中找出有@Configuration的类作为候选集
  2. 排序
  3. 遍历候选集
    1. 解析
    2. 加载解析完的BeanDefinition
    3. 如果有新的BeanDefinition被加载,需要判断其是否被@Configuration标记,如果是则加入候选集

在执行这个方法前,被@SpringBootApplication标记的类(即项目的入口类)的BeanDefinition已经被注册到了Spring中,所以在第一步中就会被加入到候选集中。

在接下来的步骤解析和加载BeanDefinition时,会通过特定的规则进行扫描,详细内容后文继续介绍,主要是55行的ConfigurationClassParser#parse和70行的ConfigurationClassBeanDefinitionReader#loadBeanDefinitions两个方法。

解析配置类

上文提到过,ConfigurationClassPostProcessor在处理BeanDefinition时,需要进行解析操作,而解析操作是委托给ConfigurationClassParser类处理的,这个类的作用通过类名就可以知道,就是用来解析配置类的,我们看一下其parse方法的具体实现:

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
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
// 依次解析所有候选BeanDefinition
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
} else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
} else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// 处理前面在解析@Import注解中为DeferredImportSelector子类的对象
processDeferredImportSelectors();
}

parse方法依次遍历了所有的候选配置类,然后根据类型的不同,调用了不同的解析方法,其实三个分支最终都是调用了同一个实现方法,只是要根据类型不同做不同的准备。通过追踪代码,可以发现其最终调用的是processConfigurationClass方法。

接下来单独分析一下processConfigurationClassprocessDeferredImportSelectors两个方法。

processConfigurationClass

先贴上代码:

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
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 如果当前类使用了@Conditional注解,则需要根据条件判断是否要跳过该类的解析
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}

ConfigurationClass existingClass = this.configurationClasses.get(configClass);
//判断当前类是否已经解析过,防止重复解析
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
//如果是被@Import注解导入的,那么记录一下
existingClass.mergeImportedBy(configClass);
}
// 直接结束了,不需要重复解析
return;
} else {
// 如果当前类不是被其他类通过@Import注解导入的,说明其是被显式定义的(说明一个类被定义了两次),那么将旧的移除。(重新解析一次)
this.configurationClasses.remove(configClass);
for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext(); ) {
if (configClass.equals(it.next())) {
it.remove();
}
}
}
}

// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
// 进行递归解析,直到该类没有父类为止,重头戏,细讲
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);

this.configurationClasses.put(configClass, configClass);
}

这个方法主要做了三件事:

第一判断当前类是否需要解析,判断委托给了ConditionEvaluator类进行处理,这个类型是根据当前类的@Conditional注解进行处理的,关于这个注解的使用如果有不了解的读者可以自行学习一下,其用法与实现与本文主要内容无关,在此不再展开叙述。

第二是判断当前类是否已经被加载过,如果是被@Import依赖的,那么记录一下就直接返回不重复处理了;如果不是被@Import依赖的,那么就再解析一遍(会把上一次的解析结果覆盖)。

第三就是具体解析的调用,回调用doProcessConfigurationClass方法进行处理,可以发现这个方法被一个循环所包围,因为方法的会返回当前类型的父类,如果其父类存在,则会循环解析,知道不存在父类时,会返回null

那么看一下doProcessConfigurationClass方法的实现,这个方法基本已经到了本文的核心逻辑,读者先根据注释对其有大概了解:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
// 递归处理当前类的内部类
processMemberClasses(configClass, sourceClass);

// 处理@PropertySource注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
// 解析PropertySource注解中的value字段,依次加载配置文件
processPropertySource(propertySource);
} else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}

// 处理@ComponentScan注解
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// @Conditional注解优先判断
for (AnnotationAttributes componentScan : componentScans) {
// 根据@ComponentScan中的参数进行扫描,实际上是委托给ClassPathBeanDefinitionScanner处理
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 如果新解析到的BeanDefinition使用了@Configuration,直接递归解析
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}

// 处理@Import注解
processImports(configClass, sourceClass,
//提取出@Import注解需要导入的类型
getImports(sourceClass),
true);

// 处理@ImportResource注解
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
// 将配置的资源存起来,后面会统一处理
configClass.addImportedResource(resolvedResource, readerClass);
}
}

// 处理@Bean注解
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
//解析添加了@Bean注解的放放,并存起来,后面统一处理
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

// 递归查询并处理接口中的@Bean注解
processInterfaces(configClass, sourceClass);

// 判断是否有父类,如果有父类,则返回,外层会递归调用;如果没有则返回null,结束解析
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}

// No superclass -> processing is complete
return null;
}

第三行调用processMemberClasses方法用来处理当前类的内部类,获取当前类的内部类,并循环递归调用processConfigurationClass方法。这个方法的具体实现感兴趣的读者可自行阅读,比较简单不再展开。

上文说到当前方法是本文的核心方法,是因为可以发现该方法的代码中,依次处理了配置类中所需要处理的五个注解,下文只会大概介绍其处理的原理,有些细节不再展开。

处理@PropertySource

@PropertySource用来实现将指定的配置文件加载到当前Spring环境中

处理@ComponentScan

@ComponentScan的作用是自动扫描指定包中的所有类,并根据其是否有特定注解(例如@Service@Component等)将其转化为BeanDefinition加载当上下文中。

在此处的实现方式是将注解中配置的包路径依次委托给ClassPathBeanDefinitionScanner进行处理,关于ClassPathBeanDefinitionScanner已经在Spring常用工具一文中大致介绍过,不再赘述。

在这一步如果解析到了新的BeanDefinition且使用了@Configuration注解,直接调用parse方法进行递归解析。

处理@Import

@Import可以将其他类引入当前上下文中,在该方法中,先通过getImports方法解析需要导入的类型,再调用processImports方法处理这些类型

先贴一下processImports的代码:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

if (importCandidates.isEmpty()) {
return;
}

if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//被导入的类型分为三类,依次处理
if (candidate.isAssignable(ImportSelector.class)) {
// 如果导入的是ImportSelector类型,则将其实例化,并调用其selectImports获取到真实需要导入的类名
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
// 如果是DeferredImportSelector的子类,就将其放入deferredImportSelectors中,延迟加载,后面会处理
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
} else {
// 非DeferredImportSelector子类,直接调用selectImports进行处理
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
// 如果导入的类型是ImportBeanDefinitionRegistrar的子类,实例化并调用其Aware接口,将这些实例存起来,后面会统一处理
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 即不是ImportSelector也不是ImportBeanDefinitionRegistrar的情况,当做有@Configuration注解的类处理,递归解析
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}

processImports方法中,循环处理每个需要导入的类型,根据其类型分为三种处理方式:

如果导入的类实现了ImportSelector接口:ImportSelector是一个动态导入接口,可以实现其selectImports方法,在该方法中根据条件返回最终需要导入的类。而在当前方法的实现则是会实例化这个ImportSelector子类,调用其selectImports方法获取需要导入的类型,并递归调用processImports方法。

不过此处有个例外,如果导入类实现了DeferredImportSelector接口,则不会在此处直接调用其selectImports方法,而会延迟调用,在此处只是进行记录,具体调用时机会在后文中提到。

如果导入类实现了ImportBeanDefinitionRegistrar接口:关于ImportBeanDefinitionRegistrarSpring支持的扩展接口中介绍过,也提到了该类是由@Import引入并可使用的。具体实现是在此处将其实例化,并调用了几个Aware接口,再将其暂存起来,后文会再次提到。

如果不是上述的两种接口的子类:将该类当做普通的@Configuration注解的类处理,递归解析。

处理@ImportResource

使用@ImportResource可以将xml类型的配置导入并解析到当前项目中,但是在此处并没有真正进行解析,也将其暂存起来,在后面会统一处理。

处理@Bean

@Bean是通过注解方式进行Bean定义最常用的方式,在此处扫描所有加了@Bean注解的方法并将其暂存,后面统一处理。

处理DeferredImportSelectors接口实现

上文提到,对于通过@Import导入的DeferredImportSelector子类需要延迟处理,正是在该方法中进行加载,处理逻辑基本与处理ImportSelector一致,不再赘述

加载BeanDefinition

读者需要把思路重新回到ConfigurationClassBeanDefinitionReader#processConfigBeanDefinitions方法中,前文我们已经把ConfigurationClassParser#parse方法分析完了,接下来就是ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法。

这里调用的方法名称为loadBeanDefinitions,直译过来就是加载BeanDefinition,但其实根据上面的阅读可以发现,前面解析时已经加载了很多BeanDefinition了,但是对于有些情况只做了记录,没有真正进行加载,而处理这些工作,正是ConfigurationClassBeanDefinitionReader#loadBeanDefinitions做的事情。

贴一下代码:

1
2
3
4
5
6
7
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
// 再遍历一次候选类,加载之前未完成的工作
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}

这个方法很简单,遍历一下候选集,然后依次调用loadBeanDefinitionsForConfigurationClass方法,追踪到里面看一下:

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
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}

if (configClass.isImported()) {
// 如果当前类是被@Import进来的,那么当前类型需要注册成BeanDefinition
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//依次加载@Bean注解的方法
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 加载@ImportResource注解对应的资源
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 处理通过@Import导入的ImportBeanDefinitionRegistrar类型
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

上面代码注释里面其实已经写得很清晰了,此处就是把上面遗留下来的几件事处理完成,分别是:

  1. 将被@Import引入的类自身注册成BeanDefinition
  2. 将被@Bean注解的方法解析成BeanDefinition并注册
  3. 加载被@ImportResource依赖的配置文件
  4. @Import导入的ImportBeanDefinitionRegistrar类在此处处理

其中配置文件的加载,是被委托给对应的BeanDefinitionReader加载的,例如xml文件被委托给XmlBeanDefinitionReader处理,这个过程与传统的Spring项目的启动时加载配置文件的过程是一样的。

对于ImportBeanDefinitionRegistrar子类的处理过程是依次调用了其registerBeanDefinitions方法,而其子类可以在这个方法中动态加载BeanDefinition

总结

至此,本文已经大致把SpringBoot中加载BeanDefinition的过程基本理了一遍,跟之前的源码分析风格一致,没有深入一些细节进行分析,尤其是本文的目的是介绍跟传统Spring容器加载BeanDefinition的差异,所以一些类似的功能就也没必要着重讲了。

如果这篇文章对你有帮助,可以请作者喝杯咖啡~