Spring解析配置類和掃描包路徑的詳細過程
目標
這是我們使用注解方式啟動spring容器的核心代碼
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); User user = (User) applicationContext.getBean("user"); user.printName();
其中配置類MyConfig的代碼是
@ComponentScan(value = "com.mydemo") public class MyConfig { }
現(xiàn)在我們的目標是搞清楚spring是怎么解析這個配置類并且掃描該配置類包路徑下的bean?
重要的組件
- AnnotatedBeanDefinitionReader : spring容器啟動的時候就會創(chuàng)建這個讀取器,主要是將類以BeanDefinition的方式保存到bean工廠(DefaultListableBeanFactory)
在創(chuàng)建這個讀取器的時候,spring會默認添加一個ConfigurationClassPostProcessor
的BeanDefinition,這個就是在解析配置類時的主要對象,在AnnotationConfigUtils類的registerAnnotationConfigProcessors中實現(xiàn)
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)); }
- ClassPathBeanDefinitionScanner : 路徑掃描器,在spring啟動的時候就會創(chuàng)建,主要功能就是對類路徑進行掃描,內(nèi)含一些掃描規(guī)則,例如在創(chuàng)建時候就會內(nèi)置一個Component注解的過濾器
protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ... }
加載配置類
我們的配置類是由AnnotatedBeanDefinitionReader類的doRegisterBean方法,轉(zhuǎn)成BeanDefinition存到bean工廠的beanDefinitionMap中,基于ASM獲取一個類信息轉(zhuǎn)成BeanDefinition。
轉(zhuǎn)成的核心代碼
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
得到配置類對象的AnnotatedGenericBeanDefinition后,雖然還沒有加載類,但是已經(jīng)獲取到了類的注解信息。
雖然都是帶有BeanDefinition,但是保存到bean工廠的BeanDefinition和這個是不一樣的,這個AnnotatedGenericBeanDefinition主要是一些注解信息,并沒有類似于BeanDefinition的屬性,如是否懶加載,作用域,是否依賴等。
解析AnnotatedGenericBeanDefinition注解信息的主要代碼,主要就是讀取Lazy、Primary 、DependsOn、Description設置成屬性值
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class); if (lazy != null) { abd.setLazyInit(lazy.getBoolean("value")); } else if (abd.getMetadata() != metadata) { lazy = attributesFor(abd.getMetadata(), Lazy.class); if (lazy != null) { abd.setLazyInit(lazy.getBoolean("value")); } } if (metadata.isAnnotated(Primary.class.getName())) { abd.setPrimary(true); } AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class); if (dependsOn != null) { abd.setDependsOn(dependsOn.getStringArray("value")); } AnnotationAttributes role = attributesFor(metadata, Role.class); if (role != null) { abd.setRole(role.getNumber("value").intValue()); } AnnotationAttributes description = attributesFor(metadata, Description.class); if (description != null) { abd.setDescription(description.getString("value")); }
解析AnnotatedGenericBeanDefinition后轉(zhuǎn)成BeanDefinitionHolder才是我們要保存到bean工廠的BeanDefinition
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
如果配置類不是代理模式,就直接保存BeanDefinition到bean工廠中了,
如果是代理模式,就創(chuàng)建一個新的RootBeanDefinition保存到bean工廠中,主要實現(xiàn)的代碼在ScopedProxyUtils類createScopedProxy方法中
啟動解析組件
spring在啟動配置類掃描的任務時,是以啟動一個BeanDefinitionRegistryPostProcessor的方式調(diào)用掃描類執(zhí)行的,屬于一種組件化啟動任務類的方式
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { ... postProcessor.postProcessBeanDefinitionRegistry(registry); ... }
這個組件的實現(xiàn)類是ConfigurationClassPostProcessor,所以所有的掃描代碼都在該類的postProcessBeanDefinitionRegistry方法下
定位配置類
在bean工廠的beanDefinitionMap中遍歷每個元素來定位符合配置類的bd,規(guī)則校驗在ConfigurationClassUtils類checkConfigurationClassCandidate方法中:
- 主要是確定該bd是AnnotatedBeanDefinition類型,
- 如果beanDef不是AnnotatedBeanDefinition的實例,則進一步檢查它是否是AbstractBeanDefinition的實例并且已經(jīng)有了對應的Class對象。如果是的話,接著會檢查這個Class是否實現(xiàn)了某些特定接口(如BeanFactoryPostProcessor, BeanPostProcessor, AopInfrastructureBean, 或者EventListenerFactory)。如果確實實現(xiàn)了這些接口中的一個或多個,函數(shù)將返回false,表示不需要繼續(xù)解析。否則,它將通過AnnotationMetadata.introspect(beanClass)方法來獲取該類的注解元數(shù)據(jù)。
- 如果以上兩種情況都不滿足,代碼將嘗試通過MetadataReader從類路徑中讀取指定類名(className)的元數(shù)據(jù)。這通常涉及到加載類文件并從中提取信息。如果在這個過程中發(fā)生IO異常(例如找不到類文件),則記錄錯誤信息并返回false。
解析配置類
解析的操作是ConfigurationClassParser來完成的,所有解析的相關邏輯都在該類的processConfigurationClass方法中,主要負責解析和注冊配置類中的各種注解:
處理@PropertySource @ComponentScan @Import @ImportResour @Bean注解,這里值分析 @ComponentScan注解,因為已經(jīng)獲取到了類的元信息,所以就可以獲取@ComponentScan配置的路徑,進而進行路徑掃描,掃描是交由ComponentScanAnnotationParser組件執(zhí)行的,由ComponentScanAnnotationParser組件發(fā)起最終在ClassPathBeanDefinitionScanner類型的doScan來實現(xiàn)
掃描過程
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
通過調(diào)用findCandidateComponents方法,根據(jù)提供的基礎包名(basePackage)來查找該包及其子包下的所有符合組件掃描條件的類,并將它們作為候選組件返回。每個候選組件都是一個BeanDefinition對象,表示潛在的Spring bean:
- 構建搜索路徑:
構建一個資源模式路徑,用于指示ResourcePatternResolver在哪里查找資源。這個路徑包括了類路徑前綴、基礎包名以及資源模式(例如/**/*.class),以便于匹配所有的類文件。
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern;
- 獲取資源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
通過getResourcePatternResolver()獲取資源解析器實例,并調(diào)用其getResources方法來獲取與給定模式匹配的所有資源。這里的資源是指符合路徑模式的類文件。
- 初步篩選
遍歷每個資源,使用MetadataReaderFactory為每個資源創(chuàng)建一個MetadataReader實例,它能夠讀取類的元數(shù)據(jù)而無需加載該類到JVM中。
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource);
首先使用isCandidateComponent(metadataReader)方法初步判斷資源是否可能是一個候選組件:
AnnotationMetadata metadata = beanDefinition.getMetadata(); return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
- 類必須是獨立的(非內(nèi)部類)。
- 同時,類必須是具體的(非接口或非抽象類),或者如果是抽象類的話,它必須包含至少一個用 @Lookup 注解標記的方法。
- 確定是否創(chuàng)建為BeanDefinition
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
對于每個候選的BeanDefinition,使用scopeMetadataResolver解析其作用域(scope)信息,同時為Bean生成或獲取一個唯一的beanName
if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); }
如果候選Bean是一個AbstractBeanDefinition類型的實例,則調(diào)用postProcessBeanDefinition方法進行額外的后處理,比如應用默認值和自動裝配規(guī)則
if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); }
如果候選Bean是AnnotatedBeanDefinition類型,那么將處理常見的注解,如@Lazy, @Primary, @DependsOn, @Role, 和 @Description等
if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); }
檢查當前候選Bean是否可以被注冊到容器中,如果可以,繼續(xù)執(zhí)行以下操作:
創(chuàng)建一個BeanDefinitionHolder對象,該對象持有Bean定義、Bean名稱以及其他元數(shù)據(jù),
如果需要使用applyScopedProxyMode根據(jù)作用域代理模式來創(chuàng)建作用域代理,
將處理后的BeanDefinitionHolder添加到beanDefinitions列表,并注冊到registry中。
在checkCandidate中還有一個方法
protected boolean isCompatible(BeanDefinition newDef, BeanDefinition existingDef) { return (!(existingDef instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean (newDef.getSource() != null && newDef.getSource().equals(existingDef.getSource())) || // scanned same file twice newDef.equals(existingDef)); // scanned equivalent class twice }
檢查新的Bean定義是否與已存在的Bean定義兼容,避免重復掃描同一個文件或者類而引起的沖突。
總結(jié)
- 配置類加載:使用AnnotatedBeanDefinitionReader將配置類轉(zhuǎn)換為BeanDefinition,并通過ASM庫獲取類信息。
- 啟動解析組件:通過實現(xiàn)BeanDefinitionRegistryPostProcessor接口的ConfigurationClassPostProcessor組件來啟動配置類的解析任務。
- 定位與解析配置類:遍歷bean工廠中的所有BeanDefinition以定位配置類,并使用ConfigurationClassParser處理配置類上的各種注解,如@ComponentScan。
- 組件掃描:ClassPathBeanDefinitionScanner根據(jù)指定的基礎包名查找符合組件掃描條件的類,進行初步篩選后創(chuàng)建BeanDefinition對象,最終注冊到Spring容器中。
以上就是Spring解析配置類和掃描包路徑的詳細過程的詳細內(nèi)容,更多關于Spring解析配置類和掃描包路徑的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot淺析安全管理之Spring Security配置
安全管理是軟件系統(tǒng)必不可少的的功能。根據(jù)經(jīng)典的“墨菲定律”——凡是可能,總會發(fā)生。如果系統(tǒng)存在安全隱患,最終必然會出現(xiàn)問題,這篇文章主要介紹了SpringBoot安全管理Spring Security基本配置2022-08-08Spring Cloud zuul自定義統(tǒng)一異常處理實現(xiàn)方法
這篇文章主要介紹了Spring Cloud zuul自定義統(tǒng)一異常處理實現(xiàn),需要的朋友可以參考下2018-02-02java基于jdbc連接mysql數(shù)據(jù)庫功能實例詳解
這篇文章主要介紹了java基于jdbc連接mysql數(shù)據(jù)庫功能,結(jié)合實例形式詳細分析了jdbc連接mysql數(shù)據(jù)庫的原理、步驟、實現(xiàn)方法及相關操作技巧,需要的朋友可以參考下2017-10-10Java線程基本使用之如何實現(xiàn)Runnable接口
這篇文章主要介紹了Java線程基本使用之如何實現(xiàn)Runnable接口問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01Java中TreeSet、HashSet、Collection重寫比較器的實現(xiàn)
比較器是一種可以對集合或數(shù)組中的元素按照自定義的方式進行排序的對象,本文主要介紹了Java中TreeSet、HashSet、Collection重寫比較器的實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下2023-08-08