tips: ConfigurationClassParser 是 Springframework 中的重要類。
本章主要是源碼理解,有難度和深度,也枯燥乏味,可以根據實際情況選擇閱讀。
位置:org.springframework.context.annotation.ConfigurationClassParser
ConfigurationClassParser 它是解密 configuration 的關鍵。理解 ConfigurationClassParser 對理解整個 Spring 框架至關重要。
一、作用是什么
ConfigurationClassParser
是一個非常重要的類,它主要用于解析帶有@Configuration
注解的類。
@Configuration
注解表明該類用作配置類,其中可以定義bean和Spring容器應如何初始化和管理這些bean。
ConfigurationClassParser
的作用可以從以下幾個方面詳細闡述:
- 解析導入的配置:
@Import
注解允許一個配置類導入另一個配置類。ConfigurationClassParser
解析這些@Import
注解,確保所有導入的配置也被處理和應用。 - 處理屬性注入:通過
@PropertySource
注解,可以指定一些屬性文件,這些屬性文件中的屬性可以被注入到Spring管理的bean中。ConfigurationClassParser
負責解析這些注解,并確保屬性文件被加載且其值可用于注入。 - 處理
@Conditional
注解: Spring框架允許在bean的注冊過程中使用條件邏輯,@Conditional
注解及其派生注解(例如@ConditionalOnClass
,@ConditionalOnProperty
等)使得只有在滿足特定條件時,才會進行bean的注冊。ConfigurationClassParser
負責解析這些條件注解并應用其邏輯。 - processDeferredImportSelectors#processImports 處理擴展配置( Starter 能夠被處理的核心分支)
二、觸發時機
SpringBoot 應用啟動過程中,通過后置處理器去觸發 ConfigurationClassPostProcessor。 然后再調用 ConfigurationClassParser
類解析
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {....
}
處理如下:
下面我們將詳細分析源碼流程。
三、ConfigurationClassPostProcessor
下面是 ConfigurationClassPostProcessor 部分核心代碼。
入口方法 processConfigBeanDefinitions(BeanDefinitionRegistry registry) 。開始分析這段代碼。
注意這里有一個 do...while
do {.....}while (!candidates.isEmpty());
它將逐一識別和解析配置類,然后將配置類中定義的Bean注冊到Spring容器中。這個過程通過不斷循環直到沒有新的配置類候選者出現為止,確保了所有相關的配置都被完整地處理。
// 創建一個配置類解析器,用于解析和處理配置類信息
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 初始化一個集合,用于存儲待處理的配置類候選者
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// 初始化一個集合,用于跟蹤已經解析過的配置類,以避免重復解析
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());// 循環處理,直到沒有新的配置類候選者
do {// 解析當前候選者中的配置類parser.parse(candidates);// 對解析結果進行驗證parser.validate();// 從解析器中獲取已解析的配置類集合Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());// 移除已經處理過的,避免重復處理configClasses.removeAll(alreadyParsed);// 如果讀取器未初始化,創建一個配置類Bean定義讀取器if (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 加載并注冊配置類中定義的Beanthis.reader.loadBeanDefinitions(configClasses);// 將這批配置類標記為“已解析”alreadyParsed.addAll(configClasses);// 清空候選者集合,為下一輪尋找新候選者做準備candidates.clear();// 檢查是否有新的Bean定義被注冊(可能由@Configuration類引入)if (registry.getBeanDefinitionCount() > candidateNames.length) {// 重新獲取所有Bean定義的名稱String[] newCandidateNames = registry.getBeanDefinitionNames();// 創建一個舊候選名稱的集合,用于辨識新的候選者Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));// 創建一個集合,用于跟蹤已經解析的配置類的類名Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}// 遍歷新的Bean定義名稱,尋找新的配置類候選者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));}}}// 更新候選名稱列表,以反映新的Bean定義candidateNames = newCandidateNames;}
// 如果還有未處理的候選者,繼續循環
} while (!candidates.isEmpty());
特別說明,對于 Bean 的加載和實例化不在本范圍了,不進行講解。感興趣可以閱讀相關章節。
上面的這段代碼是 ConfigurationClassPostProcessor 核心。解析來的重頭戲。ConfigurationClassParser
四、ConfigurationClassParser
從這行代碼開始入手parser.parse(candidates);
- parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
- processDeferredImportSelectors(); // 處理前面推遲的ImportSelector,一些二方包的導入類,將在這個方法中實現。 例如,我們配置在 Starter Spring.factories 中的自動導入類,將在這一環境被加載
parse方法入口
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {// 如果這個配置類應該根據條件注解被跳過,則直接返回不進行處理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()) {existingClass.mergeImportedBy(configClass);}// 如果已存在的配置類不是導入的,則忽略當前導入的配置類,保留現有的非導入類return;}else {// 如果找到顯式的bean定義,可能是意在替換一個導入的類。// 移除舊的配置類,采用新的配置類。this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// 遞歸處理配置類及其超類層次結構SourceClass sourceClass = asSourceClass(configClass);do {// 處理當前配置類并更新sourceClass為配置類的超類,準備下一輪處理sourceClass = doProcessConfigurationClass(configClass, sourceClass);} while (sourceClass != null); // 如果sourceClass為null,表示超類已經處理完畢// 將處理完的配置類放入配置類映射中,標記為已處理this.configurationClasses.put(configClass, configClass);
}
上面可以理解,解析 MyApplication 類所在工程中的類。最終的解析由 doProcessConfigurationClass 實現
processDeferredImportSelectors
負責處理那些被延遲的特殊接口,使用它來按需動態地導入配置。
這些常常依賴于某些條件才被執行,所以被延遲處理。
// 定義處理延遲的ImportSelector的方法
private void processDeferredImportSelectors() {// 獲取之前收集的所有延遲處理的ImportSelectorList<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;// 將引用置為null,表示開始處理過程,防止重復處理this.deferredImportSelectors = null;// 如果沒有需要處理的延遲ImportSelector,則直接返回if (deferredImports == null) {return;}...... // 遍歷所有的延遲ImportSelectorfor (DeferredImportSelectorHolder deferredImport : deferredImports) {// 獲取與當前ImportSelector相關聯的配置類ConfigurationClass configClass = deferredImport.getConfigurationClass();try {// 調用ImportSelector的selectImports方法,獲取所有的導入類名String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());// 處理這些導入的類,將它們作為配置類進行進一步的處理processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);}}
}
這個方法的主要作用將是找出符合條件的 imports 類。最終還是由processImports() 處理。
到這里,spring.factories 符合條件的一些類將被加載。核心代碼deferredImport.getImportSelector().selectImports(configClass.getMetadata());
到這里我們基本上了解了 Starter 是如何被引入進來的。
真正解析的方法 doProcessConfigurationClass
doProcessConfigurationClass
它遞歸地處理嵌套類、處理@PropertySource注解、處理@ComponentScan注解、處理@Import注解、處理@ImportResource注解、處理@Bean方法、處理接口上的默認方法,最后處理父類
- 處理成員類:方法首先遞歸地處理配置類中定義的任何成員類(嵌套類)。
- 處理
@PropertySource
注解:然后遍歷配置類上的所有@PropertySource
注解,這些注解用來指明屬性文件的位置。如果當前的環境實現了ConfigurableEnvironment
接口,則處理注解指定的屬性源 - 處理
@ComponentScan
注解:接下來,處理配置類上的所有@ComponentScan
注解,這些注解指示Spring掃描特定包下的組件(即帶有@Component
、@Service
等注解的類),并注冊為Spring容器中的Bean。如果有條件注解指示在此階段跳過處理,則不執行掃描。 - 處理
@Import
注解:處理配置類上的@Import
注解,這些注解用來導入其他配置類或配置選擇器,允許模塊化地組織配置。 - 處理
@ImportResource
注解:處理配置類上的@ImportResource
注解,這些注解用于導入XML配置文件。 - 處理
@Bean
方法:收集配置類中所有帶有@Bean
注解的方法的元數據,并將它們添加到配置類對象中。這些方法定義了應該由Spring容器管理的Bean。 - 處理接口上的默認方法:如果配置類實現了接口,并在這些接口上定義了默認方法,這些方法也會被處理。
- 處理父類:最后,如果配置類有超類,那么這個方法會檢查超類是否也是一個配置類,不是Java內置類,并且還沒有被處理過。如果滿足條件,則遞歸地處理這個超類。
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)) {// 如果環境實現了ConfigurableEnvironment接口if (this.environment instanceof ConfigurableEnvironment) {// 處理@PropertySource注解processPropertySource(propertySource);} else {// 如果環境沒有實現ConfigurableEnvironment接口logger.warn("忽略了[" + sourceClass.getMetadata().getClassName() +"]上的@PropertySource注解。原因:環境必須實現ConfigurableEnvironment接口");}}// 處理所有的@ComponentScan注解Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);// 如果存在@ComponentScan注解,并且當前階段不應該跳過if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {// 循環處理每個@ComponentScan注解for (AnnotationAttributes componentScan : componentScans) {// 配置類上存在@ComponentScan注解 -> 立即執行掃描Set<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// 檢查掃描結果中的定義集合,如果有進一步的配置類,遞歸解析for (BeanDefinitionHolder holder : scannedBeanDefinitions) {if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());}}}}// 處理所有的@Import注解processImports(configClass, sourceClass, getImports(sourceClass), true);// 處理所有的@ImportResource注解AnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);// 如果@ImportResource注解存在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);}}// 處理單獨的@Bean方法Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);// 循環處理每個@Bean方法for (MethodMetadata methodMetadata : beanMethods) {// 添加@Bean方法到配置類configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// 處理接口上的默認方法processInterfaces(configClass, sourceClass);// 處理父類,如果存在的話if (sourceClass.getMetadata().hasSuperClass()) {// 獲取父類名稱String superclass = sourceClass.getMetadata().getSuperClassName();// 如果父類存在,并且父類不是java.*開頭,并且尚未處理過if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {// 記錄已知的父類this.knownSuperclasses.put(superclass, configClass);// 找到父類,返回其注解元數據并遞歸處理return sourceClass.getSuperClass();}}// 沒有父類 -> 處理完成return null;
}
到這里,大體流程我們已經清楚。不再繼續針對注解的解析進行講解,感興趣可以自行下載源碼閱讀理解。
五、本章小結
本章是對整 ConfigurationClassParser 進行講解,它是 Spring framework 中的最核心類。
到這里,Starter 的整個過程已經分析完成,但是針對條件裝配,我們將在下一章進行講解。
?已同步發布到公眾號:面湯放鹽?第七節 ConfigurationClassParser 源碼分析 (qq.com)
掘金賬號:第七節 ConfigurationClassParser 源碼分析 - 掘金 (juejin.cn)