【Spring】簡述@Configuration配置類註冊BeanDefinition到Spring容器的過程
概述
本文以SpringBoot應用為基礎,嘗試分析基於註解@Configuration
的配置類是如何向Spring容器註冊BeanDefinition的過程
其中主要分析了 ConfigurationClassPostProcessor
這個BeanDefinitionRegistryPostProcessor
即Bean定義註冊後置處理器,在Spring啟動過程中對@Configuration配置類的處理,主要體現在 解析並發現所有配置類,處理配置類的相關邏輯(如配置類上的@ComponentScan、@Import、@Bean註解等),註冊其中的BeanDefinition
SpringBoot版本:2.0.9.RELEASE
Spring版本:5.0.13.RELEASE
ConfigurationClassPostProcessor如何被引入
首先看一下ConfigurationClassPostProcessor的類繼承關係
從紅框中可以看出ConfigurationClassPostProcessor
是BeanDefinitionRegistryPostProcessor
介面的實現類,即是一個Bean定義註冊的後置處理器,會在Spring容器啟動時被呼叫,具體時機為
// 呼叫鏈 AbstractApplicationContext.refresh() => invokeBeanFactoryPostProcessors() => PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()
invokeBeanFactoryPostProcessors()
會先呼叫所有的BeanDefinitionRegistryPostProcessor
之後,再呼叫所有的BeanFactoryPostProcessor
ConfigurationClassPostProcessor又是如何被引入Spring的呢??
SpringBoot應用會在ApplicationContext應用上下文被建立的建構函式中new AnnotatedBeanDefinitionReader
這個用於註冊基於註解的BeanDefinition的Reader,在其構造中又會呼叫AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)
ConfigurationClassPostProcessor
// ConfigurationClassPostProcessor被註冊
AnnotationConfigServletWebServerApplicationContext構造
=> new AnnotatedBeanDefinitionReader(registry)
=> AnnotationConfigUtils.registerAnnotationConfigProcessors(registry)
=> 註冊ConfigurationClassPostProcessor到Spring容器
ConfigurationClassPostProcessor處理過程簡述
首先,ConfigurationClassPostProcessor後置處理器的處理入口為postProcessBeanDefinitionRegistry()
方法。其主要使用了ConfigurationClassParser
配置類解析器解析@Configuration
配置類上的諸如@ComponentScan
、@Import
、@Bean
等註解,並嘗試發現所有的配置類;還使用了ConfigurationClassBeanDefinitionReader
註冊所發現的所有配置類中的所有Bean定義;結束執行的條件是所有配置類都被發現和處理,相應的bean定義註冊到容器
大致流程如下:
1、通過BeanDefinitionRegistry查詢當前Spring容器中所有BeanDefinition
2、通過ConfigurationClassUtils.checkConfigurationClassCandidate()
檢查BeanDefinition是否為 “完全配置類” 或 “簡化配置類”,並對配置類做標記,放入集合待後續處理
Spring配置類的分類可以 參考
3、通過 ConfigurationClassParser解析器
parse解析配置類集合,嘗試通過它們找到其它配置類
4、使用 ConfigurationClassBeanDefinitionReader
註冊通過所發現的配置類中找到的所有beanDefinition
5、處理完一輪配置類後,檢視BeanDefinitionRegistry中是否存在新載入的且還未被處理過的 “完全配置類” 或 “簡化配置類”,有的話繼續上面步驟
其中第3、4步後面重點分析
ConfigurationClassParser#parse():解析構建配置類
對於SpringBoot應用來說,參與解析的種子配置檔案即為SpringBoot的Application啟動類
解析構建配置類流程
通過ConfigurationClassParser解析器parse解析配置類集合,嘗試通過它們找到其它配置類
迴圈解析所有配置類 ConfigurationClassParser#processConfigurationClass()
根據@Conditional的ConfigurationPhase.PARSE_CONFIGURATION階段條件判斷是否跳過配置類
注意:有些@Conditional是在當前這個PARSE_CONFIGURATION解析配置階段使用的,有些是在REGISTER_BEAN註冊beanDefinition階段使用的
【重點】呼叫ConfigurationClassParser#doProcessConfigurationClass()迴圈解析配置類,直到不存在未處理過的父類
- 1、處理配置類的成員內部類: 檢查其是否為“完全/簡化配置類”,是則對其繼續分析處理並將其放入分析器的屬性configurationClasses
- 2、處理@PropertySource: 將找到的PropertySource新增到environment的PropertySource集合
- 3、處理@ComponentScan: 掃描到的@Component類BeanDefinition就直接註冊到Spring容器;如果元件為配置類,繼續分析處理並將其放入分析器的屬性configurationClasses
- 4、處理@Import:
- (1)處理ImportSelector: 如果是DeferredImportSelector,如SpringBoot的自動配置匯入,新增到deferredImportSelectors,延遲進行processImports();其它通過ImportSelector找到的類,繼續呼叫processImports(),要麼是@Configuration配置類繼續解析,要麼是普通元件匯入Spring容器
- (2)處理ImportBeanDefinitionRegistrar: 呼叫當前配置類的addImportBeanDefinitionRegistrar(),後面委託它註冊其它bean定義
- (3)其它Import:呼叫processConfigurationClass()繼續解析,最終要麼是配置類放入configurationClasses,要麼是普通元件匯入Spring容器
- 5、處理@ImportResource: 新增到配置類的importedResources集合,後續ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()時再使用這些匯入的BeanDefinitionReader讀取Resource中的bean定義並註冊
- 6、處理@Bean: 獲取所有@Bean方法,並新增到配置類的beanMethods集合
- 7、處理配置類介面上的default methods
- 8、檢查是否有未處理的父類: 如果配置類有父類,且其不在解析器維護的knownSuperclasses中,對其呼叫doProcessConfigurationClass()重複如上檢查,直到不再有父類或父類在knownSuperclasses中已存在
processDeferredImportSelectors():處理推遲的ImportSelector集合,其實就是延遲呼叫了processImports()
SpringBoot的自動配置類就是被DeferredImportSelector推遲匯入的
解析構建配置類原始碼分析
ConfigurationClassParser#processConfigurationClass()
包含了處理單個配置類的大體流程,先根據ConfigurationPhase.PARSE_CONFIGURATION
解析配置階段的@Conditional
條件判斷當前配置類是否應該解析,之後呼叫ConfigurationClassParser#doProcessConfigurationClass()
迴圈解析配置類,直到不存在未處理過的父類
/**
* 解析單個配置類
* 解析的最後會將當前配置類放到configurationClasses
*/
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
/**
* 根據@Conditional條件判斷是否跳過配置類
* 注意:當前這個PARSE_CONFIGURATION解析配置階段只會使用這個階段的@Conditional條件,有些REGISTER_BEAN註冊beanDefinition階段的條件不會在此時使用
*/
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
// 如果configClass在已經分析處理的配置類記錄中已存在
if (existingClass != null) {
//如果配置類是被@Import註冊的,return
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
// 否則,清除老的記錄,在來一遍
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
/**
* 遞迴處理配置類及其超類層次結構
* 從當前配置類configClass開始向上沿著類繼承結構逐層執行doProcessConfigurationClass,直到遇到的父類是由Java提供的類結束迴圈
*/
SourceClass sourceClass = asSourceClass(configClass);
/**
* 迴圈處理配置類configClass直到sourceClass變為null,即父類為null
* doProcessConfigurationClass的返回值是其引數configClass的父類
* 如果該父類是由Java提供的類或者已經處理過,返回null
*/
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
ConfigurationClassParser#doProcessConfigurationClass():真正解析配置類
通過解析配置類上的註解、內部成員類和方法構建一個完整的ConfigurationClass配置類,過程中如果發現了新的配置類可以重複呼叫此方法
真正解析過程中會處理成員內部類
、@PropertySource
、@ComponentScan
、@Import
、@ImportSource
、@Bean方法
等,流程如下:
- 1、處理配置類的成員內部類: 檢查其是否為“完全/簡化配置類”,是則對其繼續分析處理並將其放入分析器的屬性configurationClasses
- 2、處理@PropertySource: 將找到的PropertySource新增到environment的PropertySource集合
- 3、處理@ComponentScan: 掃描到的@Component類BeanDefinition就直接註冊到Spring容器;如果元件為配置類,繼續分析處理並將其放入分析器的屬性configurationClasses
- 4、處理@Import:
- (1)處理ImportSelector: 如果是DeferredImportSelector,如SpringBoot的自動配置匯入,新增到deferredImportSelectors,延遲進行processImports();其它通過ImportSelector找到的類,繼續呼叫processImports(),要麼是@Configuration配置類繼續解析,要麼是普通元件匯入Spring容器
- (2)處理ImportBeanDefinitionRegistrar: 呼叫當前配置類的addImportBeanDefinitionRegistrar(),後面委託它註冊其它bean定義
- (3)其它Import: 呼叫processConfigurationClass()繼續解析,最終要麼是配置類放入configurationClasses,要麼是普通元件匯入Spring容器
- 5、處理@ImportResource: 新增到配置類的importedResources集合,後續ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()時再使用這些匯入的BeanDefinitionReader讀取Resource中的bean定義並註冊
- 6、處理@Bean: 獲取所有@Bean方法,並新增到配置類的beanMethods集合
- 7、處理配置類介面上的default methods
- 8、檢查是否有未處理的父類: 如果配置類有父類,且其不在解析器維護的knownSuperclasses中,對其呼叫doProcessConfigurationClass()重複如上檢查,直到不再有父類或父類在knownSuperclasses中已存在
/**
* Apply processing and build a complete {@link ConfigurationClass} by reading the
* annotations, members and methods from the source class. This method can be called
* multiple times as relevant sources are discovered.
* @param configClass the configuration class being build
* @param sourceClass a source class
* @return the superclass, or {@code null} if none found or previously processed
*/
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
/**
* 1、處理配置類的成員類(配置類內巢狀定義的類)
* 內部巢狀類也可能是配置類,遍歷這些成員類,檢查是否為"完全/簡化配置類"
* 有的話,呼叫processConfigurationClass()處理它們,最終將配置類放入configurationClasses集合
*/
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
/**
* 2、處理 @PropertySource
* 將找到的PropertySource新增到environment的PropertySource集合
*/
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
/**
* 3、處理 @ComponentScan
* 處理使用者手工新增的@ComponentScan,SpringBoot建立ApplicationContext時的ClassPathBeanDefinitionScanner是為了掃描啟動類下的包
* 為的是找到滿足條件的@ComponentScan,即@Component相關的元件,先掃描一下,掃描到的就註冊為BeanDefinition
* 看其中是否還有配置類,有的話parse()繼續分析處理,配置類新增到configurationClasses集合
*/
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
// 如果當前配置類上有@ComponentScan,且使用REGISTER_BEAN註冊beanDefinition的條件判斷也不跳過的話
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 立即掃描,掃描到的就註冊為BeanDefinition,並獲得掃描到的所有beanDefinition
// 在處理SpringBoot啟動類上的@ComponentScan時,雖然指指定了excludeFilters,但會根據啟動類所在包推測basePackage,就會掃描到SpringBoot啟動類包以下的Bean並註冊
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
// 檢查掃描到的beanDefinition中是否有配置類,有的話parse()繼續分析處理,,配置類新增到configurationClasses集合
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
/**
* 4、處理 @Import
* (1)處理ImportSelector
* 如果是DeferredImportSelector,如SpringBoot的自動配置匯入,新增到deferredImportSelectors,延遲進行processImports()
* 其它通過ImportSelector找到的類,繼續呼叫processImports(),要麼是@Configuration配置類繼續解析,要麼是普通元件匯入Spring容器
* (2)處理ImportBeanDefinitionRegistrar
* 呼叫當前配置類的addImportBeanDefinitionRegistrar(),後面委託它註冊其它bean定義
* (3)其它
* 呼叫processConfigurationClass()繼續解析,最終要麼是配置類放入configurationClasses,要麼是普通元件匯入Spring容器
*/
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
/**
* 5、處理 @ImportResource
* 新增到配置類的importedResources集合,後續loadBeanDefinitions()載入bean定義時再讓這些匯入BeanDefinitionReader自行讀取bean定義
*/
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
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);
}
}
// Process individual @Bean methods
/**
* 6、處理個別@Bean方法
* 獲取所有@Bean方法,並新增到配置類的beanMethods集合
*/
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
/**
* 7、處理配置類介面上的default methods
*/
processInterfaces(configClass, sourceClass);
// Process superclass, if any
/**
* 8、檢查父類是否需要處理,如果父類需要處理返回父類,否則返回null
* 如果存在父類,且不在knownSuperclasses已經分析過的父類列表裡,返回並繼續分析
*/
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !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;
}
ConfigurationClassBeanDefinitionReader#loadBeanDefinitions():讀取配置類,基於配置資訊註冊BeanDefinition
讀取配置類,基於配置資訊註冊BeanDefinition流程
在上面解析配置類的過程中,除了構建了一個完整的ConfigurationClass配置類,其實已經向BeanDefinitionRegistry
中添加了一些beanDefinition了,比如在處理@ComponentScan
時,掃描到的@Component相關元件
就已經註冊了
而ConfigurationClassBeanDefinitionReader
會繼續讀取已經構建好的ConfigurationClass配置類中的成員變數,從而註冊beanDefinition
構建好的ConfigurationClass配置類中在本階段可用的成員變數包括:
Set<BeanMethod> beanMethods
: @Bean的方法Map<String, Class<? extends BeanDefinitionReader>> importedResources
:配置類上@ImportResource註解的類存入此集合,會使用BeanDefinitionReader讀取Resource中的BeanDefinition並註冊Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars
:ImportBeanDefinitionRegistrar集合
通過構建好的配置類的配置資訊,使用ConfigurationClassBeanDefinitionReader註冊所有能夠讀取到的beanDefinition:
根據
ConfigurationPhase.REGISTER_BEAN階段
條件判斷配置類是否需要跳過迴圈判斷配置類以及匯入配置類的類,使用ConfigurationPhase.REGISTER_BEAN階段條件判斷是否需要跳過只要配置類或匯入配置類的類需要跳過即返回跳過
如果configClass.isImported(),將配置類自身註冊為beanDefinition
註冊配置類所有
@Bean方法
為beanDefinition註冊由
@ImportedResources
來的beanDefinition,即通過其它型別Resource的BeanDefinitionReader
讀取BeanDefinition並註冊,如xml格式的配置源XmlBeanDefinitionReader
註冊由
ImportBeanDefinitionRegistrars
來的beanDefinition
讀取配置類,基於配置資訊註冊BeanDefinition原始碼分析
/**
* Read a particular {@link ConfigurationClass}, registering bean definitions
* for the class itself and all of its {@link Bean} methods.
* 讀取特定配置類,根據配置資訊註冊bean definitions
*/
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
/**
* 根據ConfigurationPhase.REGISTER_BEAN階段條件判斷配置類是否需要跳過
* 迴圈判斷配置類以及匯入配置類的類,使用ConfigurationPhase.REGISTER_BEAN階段條件判斷是否需要跳過
* 只要配置類或匯入配置類的類需要跳過即返回跳過
*/
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;
}
// 1、如果當前配置類是通過內部類匯入 或 @Import匯入,將配置類自身註冊為beanDefinition
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// 2、註冊配置類所有@Bean方法為beanDefinition
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 3、註冊由@ImportedResources來的beanDefinition
// 即通過其它型別Resource的BeanDefinitionReader讀取BeanDefinition並註冊
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 4、註冊由ImportBeanDefinitionRegistrars來的beanDefinition
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
思維導圖
請放大觀看
參考
Spring BeanDefinitionRegistryPostProcessor:ConfigurationClassPostProcessor
Spring 工具類 ConfigurationClassParser 分析得到配置類
Spring 配置類的分