SpringBoot 版本 : 2.2.1.RELEASE
Spring 版本 : 5.2.1.RELEASE
入口類: SpringApplication;SpringApplicationBuilder
說明 : 由于SpringBoot建立在Spring之上,所以分析SpringBoot的啟動過程其實與Spring是交錯進行的,分析的時候會順帶將一些Spring的擴展點也提到
注:本文主要講解一些比較重要的關鍵步驟,不能面面俱到,若有疑問,隨時保持溝通
SpringBoot啟動 源碼深度解析(一)
SpringBoot啟動 源碼深度解析(二)
SpringBoot啟動 源碼深度解析(四)
-
下面來看核心的配置類處理器ConfigurationClassPostProcessor流程:進入到: org.springframework.context.annotation.ConfigurationClassPostProcessor
#postProcessBeanDefinitionRegistry方法中:當前后置處理器的作用是解析bean定義配置類,實現IOC,
。進到方法org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions中執行處理邏輯為:- ??????獲取已經注冊的bean名稱,根據名稱獲取所有的bean定義,循環判斷當前bean的屬性是否是全量( 屬性名稱為
ConfigurationClassPostProcessor.configurationClass
對應的屬性值為full
)或者是輕量的( 屬性名稱為ConfigurationClassPostProcessor.configurationClass
對應的屬性值為lite
),若不滿足再對當前bean定義做checkConfigurationClassCandidate類型檢查,方法中首先會檢查是否是AnnotatedBeanDefinition實例并且class名稱與元數據中緩存的class名稱要相同才會重用bean定義中的元數據。最后若重新生成的元數據包含@configuration注解,那么設置屬性為full,若包含的屬性包含@Component、@Bean、@Import、@ImportSource、@ComponentScan,設置屬性為lite并返回true。那么此時會將當前bean定義添加到configCandidates集合中,然后獲取當前bean定義的ConfigurationClassPostProcessor.order屬性的數值進行排序。然后判斷是否有自定義的單例bean生成策略。 - ??????下面開始正式解析被@Configuration或者
普通注解
標注的類:創建配置類解析器ConfigurationClassParser實例
(構造器會將ComponentScanAnnotationParser 解析器對象一起創建,用于解析@ComponentScan),接著調用org.springframework.context.annotation.ConfigurationClassParser
#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)解析方法,根據bean定義的不同,創建不同的ConfigurationClass實例對象
,然后統一調用processConfigurationClass方法做循環解析處理: -
通過conditionEvaluator判斷配置階段類型是
ConfigurationPhase.PARSE_CONFIGURATION
的配置類是否帶有@Conditional注解,并做解析校驗,判斷是否需要跳過. - 從緩存獲取當前配置類是否被導入,若導入并且當前的配置類也被導入,則將當前的配置類添加到緩存的配置類中進行合并,若當前配置類沒有被導入,則將舊的配置類從緩存中移除,目的是對配置類進行校驗。
- ????????調用
org.springframework.context.annotation.ConfigurationClassParser
#doProcessConfigurationClass方法中,首先判斷配置類有沒有被@Component標注(包括@Configuration),有的話,調用org.springframework.context.annotation.ConfigurationClassParser
#processMemberClasses 首先處理嵌套的成員類(類中套類示例)
image.png - ????????處理@PropertySource注解,通過AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class,PropertySource.class)獲取元數據標注的所有@PropertySource注解,然后調用
org.springframework.context.annotation.ConfigurationClassParser
#processPropertySource去處理每個propertySource
。包括解析占位符、然后將屬性添加到propertySourceNames集合中。 - ????????處理@ComponentScan注解,獲取方式與上面一樣,若獲取的集合不為空并且當前條件判斷
ConfigurationPhase.REGISTER_BEAN
的注冊bean階段不會跳過流程,則依次遍歷所有的結果集,調用org.springframework.context.annotation.ComponentScanAnnotationParser
#parse立刻執行掃描過程,解析器會將@ComponentScan注解屬性解析到ClassPathBeanDefinitionScanner對象中
。ClassPathBeanDefinitionScanner(會掃描@Component、@Repository、@Service、@Controller、javax.annotation.ManagedBean、javax.inject.Named 等注解)。 然后調用org.springframework.context.annotation.ClassPathBeanDefinitionScanner
#doScan( StringUtils.toStringArray(basePackages) )** 開始進行掃描處理。接著調用org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
#findCandidateComponents( String basePackage ) 在當前路徑下查找候選components。解析basePackage 的占位符和轉換包名對應的 · 為 / ,即:
// @ComponentScan("com.ljj.sourcecode.analysis")包路徑處理:
// packageSearchPath = classpath*:com/ljj/sourcecode/analysis/**/*.class
然后會解析當前路徑下的所有資源(包括依賴的jar)。然后調用isCandidateComponent(metadataReader) 判斷是否是候選的組件( 在ClassPathBeanDefinitionScanner初始化的時候,會往 this.includeFilters 集合中添加一個new AnnotationTypeFilter(Component.class) 實例
),代碼如下:
image.png
org.springframework.context.annotation.ClassPathScanningCandidateCompon entProvider#isConditionMatch 判斷是否需要跳過,返回boolean結果。最
?后創建ScannedGenericBeanDefinition實例化對象。再次判斷是否是候選components,
?若是的話將ScannedGenericBeanDefinition實例添加到candidates集合中,循環完畢返
?回candidates集合.遍歷篩選出來的BeanDefinition,
接著執行
? this.scopeMetadataResolver.resolveScopeMetadata(candidate) 獲取屬性元數據;
創建bean名稱若bean定義是AbstractBeanDefinition者執行
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition方法處理生成的bean定義,如果不設置成員
autowireCandidatePatterns的值,則設置bean定義的autowireCandidate為false。 > 若bean定義是AnnotatedBeanDefinition**的實例,需要執行代碼 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate)處理注解為 @Lazy、@Primary、@DependsOn、@Role、@Description
,解析到bean定義中。然后再調用org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate方法判斷bean定義是否已經被注冊,若沒有注冊,進行后續的bean定義創建流程,若已經注冊 則返回false或者拋出bean沖突異常。遍歷結束返回所有的 beanDefinitions集合**。image.png - ??????????處理@Import注解,執行代碼
processImports(configClass, sourceClass, getImports(sourceClass), true)
,先遞歸的獲取所有注解帶有的@Import,然后參數傳入到org.springframework.context.annotation.ConfigurationClassParser
#processImports方法中。進入當前方法之后,首先對導入候選注解做一個非空判斷,集合為空直接結束@Import處理,再根據傳入的參數checkForCircularImports
判斷如果啟動循環導入檢查(true)并且被放入導入棧中(importStack
)則結束處理;否則開始解析所有的importCandidates集合。若導入候選類型為ImportSelector
,則實例化當前ImportSelector
實例,同時會判斷當前實例是否是Aware
子類型,若是,則回調具體的Aware子接口(BeanClassLoaderAware、BeanFactoryAware && 是BeanFactory的子類型、EnvironmentAware、ResourceLoaderAware),進一步判斷是否是DeferredImportSelector子接口類型
,若是,直接添加到deferredImportSelectors集合中,否則調用當前實例的selectImports
方法,執行自定義的導入處理。比如boot中自動裝配功能org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
的selectImports實現,會將spring.factories中的所有EnableAutoConfiguration.class的實現篩選出來。接下來的遞歸操作也正是AutoConfigurationImportSelector
這類篩選器的操作體現,因為執行完篩選之后,可能生成很多@Configuration類。另一種情況,若是ImportBeanDefinitionRegistrar類型的,依然是先執行這行回調代碼ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry)
。然后向
Map<ImportBeanDefinitionRegistrar, AnnotationMetadata>importBeanDefinitionRegistrars
= new LinkedHashMap<>()成員遍歷中添加當前beanDefinetionRegistrar
。??這里用org.apache.dubbo.config.spring.context.annotation.DubboConfigConfigurationRegistrar類做示例??,代碼如下:image.png
首先根據被注解類的注解元數據獲取@EnableDubboConfig的注解類型,取到屬性multiple
對應的值,先把注冊單個bean,若屬性值為true,再注冊多個bean。如果既不是ImportSelector類型也不是ImportBeanDefinitionRegistrar類型,則將資源配置元數據添加到importStack中,調用processConfigurationClass(candidate.asConfigClass(configClass))代碼,至此@Import注解解析完畢。 - ??????處理@ImportResource注解,
同樣的方式獲取ImportResource注解的屬性值,注解屬性不為空,獲取對應屬性locations、reader的值
,先處理占位符,再將設置的BeanDefinitionReader添加到緩存Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>()
中。 - ????????處理單獨的@Bean methods,
獲取被注釋@Bean的方法,若存在并且元數據注解是StandardAnnotationMetadata類型
,則嘗試使用ASM讀取并推斷聲明順序(但是由于JVM的反射機制返回的方法是任意的順序),若解析出來的bean方法不為空遍歷并添加到配置類成員 Set<BeanMethod>beanMethods
= new LinkedHashSet<>()中緩存起來。 - ????????處理接口的 default 方法對應的@Bean方法,獲取元數據class,然后獲取class實現的所有接口對應的@Bean方法,添加到configClass配置信息中。
跟上一步的區別是這一步獲取的是接口對應的@Bean方法
。 -
最后將所有的配置類信息存入
org.springframework.context.annotation.ConfigurationClassParser
#configurationClasses緩存中供使用
。image.png
-
通過conditionEvaluator判斷配置階段類型是
????總結: 獲取已經注冊的bean定義 -> 處理帶有注解得嵌套類(member)-> @PropertySources -> @ComponentScan -> @Import -> @ImportResource -> @Bean -> Java8特性 Default方法 -> 是否有父類并且父類不能是Java開頭的 -> 解析完會返回null,否則會循環解析。????
- ??????獲取已經注冊的bean名稱,根據名稱獲取所有的bean定義,循環判斷當前bean的屬性是否是全量( 屬性名稱為
??????????解析完成之后對配置類做校驗,
1. 如果含有@Configuration注解的配置類不能是final修飾 2. 如果配置類是靜態的,則直接返回校驗通過,若配置類標有@Configuration,但是不允許覆蓋(繼承、重寫方法),拋出異常,因為spring會對配置類使用CGLIB代理
。-
??????????接著調用
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitions( Set<ConfigurationClass> configClasses )將當前配置類執行注冊bean定義。調用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitionsForConfigurationClass方法,如下:image.png
1. 判斷配置類是否需要跳過,若需要跳過,則移除已經注冊的bean定義和ImportStack中的緩存。然后判斷配置類是否已經被Import了,是則調用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#registerBeanDefinitionForImportedConfigurationClass方法注冊當前配置類:image.png
2. 若當前配置類是通過@Import進來的或者是嵌套類,首先獲取配置類的注解元數據,創建一個注解普通beanDefinetion對象 AnnotatedGenericBeanDefinition,通過成員屬性 ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver()解析當前配置類的@Scope注解屬性,獲取屬性值添加到beanDefinetion中
(默認單例)。然后處理通用的bean定義注解(包括 @Lazy、@Primary、@DependsOn、@Role、@Description)封裝到bean定義中。最后創建BeanDefinitionHolder對象,根據前面解析的scope屬性判斷是否需要做代理
,通過BeanDefinitionRegistry
注冊器將bean定義注冊到bean工廠中。
3. 加載配置類的@Bean方法相關配置,迭代調用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitionsForBeanMethod方法加載bean定義。方法篇幅過大,大致流程是:判斷是否需要跳過注冊,然后獲取@Bean的屬性值,為名稱注冊別名
;接著判斷是否允許存在的bean定義被覆蓋,若允許此處會提前結束bean創建,不允許就開始創建配置類bean定義,然后進行如下判斷:image.png
判斷當前方法元數據是否是靜態的,設置beanClassName為配置類的名稱,設置工廠方法名為當前靜態方法;若不是靜態的,則設置工廠名稱為配置類的名稱,設置唯一的工廠方法名稱為當前@Bean的方法名。設置當前bean定義的自動注入模式為構造器注入,設置RequiredAnnotationBeanPostProcessor.skipRequiredCheck
跳過@Required檢查屬性的值為true。然后再判斷當前元數據是否包含通用的bean定義注解:@Lazy、@Primary、@Role、@DependsOn、@Description
,若存在將屬性設置到bean定義中,再把@Bean注解其他屬性值填充到bean定義中。
獲取當前方法對應的@Scope注解的屬性,緊接著判斷是否需要啟動代理模式,若需要根據具體的代理方式創建出代理bean定義,然后注冊到bean工廠中
。image.png
4. 加載配置類的所有導入的importSources,執行遍歷。校驗BeanDefinitionReader.class是否與緩存的Class類型相同,相同的話,在java里面會把readerClass設置為XmlBeanDefinitionReader.class
,專門用來解析XML的BeanDefinetionReader實現。然后判斷緩存中是否存在當前readerClass,不存在的話反射創建一個reader對象,同時設置資源加載器為當前資源加載器實例this.resourceLoader
,設置上下文環境this.environment
然后放到緩存中。最后調用reader.loadBeanDefinitions(resource)代碼去加載bean定義。image.png
5. 加載配置類的所有導入的ImportBeanDefinetionRegistrars。遍歷registrars然后調用registrar的registerBeanDefinitions方法,實現所有子類的自定義回調
。
總結:至此,ConfigurationClassPostProcessor后置處理器中
校驗beanDefinetion、解析beanDefinetion、加載beanDefinetion就完成了
。
注冊流程為: 校驗是否需要跳過 -> @Import導入或者是嵌套類的配置類信息 -> @Bean方法配置信息注冊 -> @ImportResource配置類信息 -> ImportBeanDefinetionRegistrar類型的配置類信息。
- ? 文章要是勘誤或者知識點說的不正確,歡迎評論,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽!
- ? 要是感覺文章對你有所幫助,不妨點個關注,或者移駕看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
- ? 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處!