SpringBoot 版本 : 2.2.1.RELEASE
Spring 版本 : 5.2.1.RELEASE
入口類: SpringApplication鳄乏;SpringApplicationBuilder
說明 : 由于SpringBoot建立在Spring之上,所以分析SpringBoot的啟動過程其實(shí)與Spring是交錯進(jìn)行的,分析的時(shí)候會順帶將一些Spring的擴(kuò)展點(diǎn)也提到
注:本文主要講解一些比較重要的關(guān)鍵步驟姥芥,不能面面俱到顿肺,若有疑問遮晚,隨時(shí)保持溝通
SpringBoot啟動 源碼深度解析(一)
SpringBoot啟動 源碼深度解析(二)
SpringBoot啟動 源碼深度解析(四)
-
下面來看核心的配置類處理器ConfigurationClassPostProcessor流程:進(jìn)入到: org.springframework.context.annotation.ConfigurationClassPostProcessor
#postProcessBeanDefinitionRegistry方法中:當(dāng)前后置處理器的作用是解析bean定義配置類性昭,實(shí)現(xiàn)IOC,
县遣。進(jìn)到方法org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions中執(zhí)行處理邏輯為:- ??????獲取已經(jīng)注冊的bean名稱,根據(jù)名稱獲取所有的bean定義汹族,循環(huán)判斷當(dāng)前bean的屬性是否是全量( 屬性名稱為
ConfigurationClassPostProcessor.configurationClass
對應(yīng)的屬性值為full
)或者是輕量的( 屬性名稱為ConfigurationClassPostProcessor.configurationClass
對應(yīng)的屬性值為lite
)萧求,若不滿足再對當(dāng)前bean定義做checkConfigurationClassCandidate類型檢查,方法中首先會檢查是否是AnnotatedBeanDefinition實(shí)例并且class名稱與元數(shù)據(jù)中緩存的class名稱要相同才會重用bean定義中的元數(shù)據(jù)顶瞒。最后若重新生成的元數(shù)據(jù)包含@configuration注解夸政,那么設(shè)置屬性為full,若包含的屬性包含@Component榴徐、@Bean守问、@Import、@ImportSource坑资、@ComponentScan耗帕,設(shè)置屬性為lite并返回true。那么此時(shí)會將當(dāng)前bean定義添加到configCandidates集合中袱贮,然后獲取當(dāng)前bean定義的ConfigurationClassPostProcessor.order屬性的數(shù)值進(jìn)行排序仿便。然后判斷是否有自定義的單例bean生成策略。 - ??????下面開始正式解析被@Configuration或者
普通注解
標(biāo)注的類:創(chuàng)建配置類解析器ConfigurationClassParser實(shí)例
(構(gòu)造器會將ComponentScanAnnotationParser 解析器對象一起創(chuàng)建攒巍,用于解析@ComponentScan)嗽仪,接著調(diào)用org.springframework.context.annotation.ConfigurationClassParser
#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)解析方法,根據(jù)bean定義的不同柒莉,創(chuàng)建不同的ConfigurationClass實(shí)例對象
闻坚,然后統(tǒng)一調(diào)用processConfigurationClass方法做循環(huán)解析處理: -
通過conditionEvaluator判斷配置階段類型是
ConfigurationPhase.PARSE_CONFIGURATION
的配置類是否帶有@Conditional注解,并做解析校驗(yàn)兢孝,判斷是否需要跳過. - 從緩存獲取當(dāng)前配置類是否被導(dǎo)入窿凤,若導(dǎo)入并且當(dāng)前的配置類也被導(dǎo)入搀擂,則將當(dāng)前的配置類添加到緩存的配置類中進(jìn)行合并,若當(dāng)前配置類沒有被導(dǎo)入卷玉,則將舊的配置類從緩存中移除哨颂,目的是對配置類進(jìn)行校驗(yàn)。
- ????????調(diào)用
org.springframework.context.annotation.ConfigurationClassParser
#doProcessConfigurationClass方法中相种,首先判斷配置類有沒有被@Component標(biāo)注(包括@Configuration)威恼,有的話,調(diào)用org.springframework.context.annotation.ConfigurationClassParser
#processMemberClasses 首先處理嵌套的成員類(類中套類示例)
image.png - ????????處理@PropertySource注解寝并,通過AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class,PropertySource.class)獲取元數(shù)據(jù)標(biāo)注的所有@PropertySource注解箫措,然后調(diào)用
org.springframework.context.annotation.ConfigurationClassParser
#processPropertySource去處理每個(gè)propertySource
。包括解析占位符衬潦、然后將屬性添加到propertySourceNames集合中斤蔓。 - ????????處理@ComponentScan注解,獲取方式與上面一樣镀岛,若獲取的集合不為空并且當(dāng)前條件判斷
ConfigurationPhase.REGISTER_BEAN
的注冊bean階段不會跳過流程弦牡,則依次遍歷所有的結(jié)果集,調(diào)用org.springframework.context.annotation.ComponentScanAnnotationParser
#parse立刻執(zhí)行掃描過程,解析器會將@ComponentScan注解屬性解析到ClassPathBeanDefinitionScanner對象中
漂羊。ClassPathBeanDefinitionScanner(會掃描@Component驾锰、@Repository、@Service走越、@Controller椭豫、javax.annotation.ManagedBean、javax.inject.Named 等注解)旨指。 然后調(diào)用org.springframework.context.annotation.ClassPathBeanDefinitionScanner
#doScan( StringUtils.toStringArray(basePackages) )** 開始進(jìn)行掃描處理赏酥。接著調(diào)用org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
#findCandidateComponents( String basePackage ) 在當(dāng)前路徑下查找候選components。解析basePackage 的占位符和轉(zhuǎn)換包名對應(yīng)的 · 為 / ,即:
// @ComponentScan("com.ljj.sourcecode.analysis")包路徑處理:
// packageSearchPath = classpath*:com/ljj/sourcecode/analysis/**/*.class
然后會解析當(dāng)前路徑下的所有資源(包括依賴的jar)谆构。然后調(diào)用isCandidateComponent(metadataReader) 判斷是否是候選的組件( 在ClassPathBeanDefinitionScanner初始化的時(shí)候裸扶,會往 this.includeFilters 集合中添加一個(gè)new AnnotationTypeFilter(Component.class) 實(shí)例
),代碼如下:
image.png
org.springframework.context.annotation.ClassPathScanningCandidateCompon entProvider#isConditionMatch 判斷是否需要跳過低淡,返回boolean結(jié)果姓言。最
?后創(chuàng)建ScannedGenericBeanDefinition實(shí)例化對象。再次判斷是否是候選components蔗蹋,
?若是的話將ScannedGenericBeanDefinition實(shí)例添加到candidates集合中何荚,循環(huán)完畢返
?回candidates集合.遍歷篩選出來的BeanDefinition,
接著執(zhí)行
? this.scopeMetadataResolver.resolveScopeMetadata(candidate) 獲取屬性元數(shù)據(jù);
創(chuàng)建bean名稱若bean定義是AbstractBeanDefinition者執(zhí)行
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition方法處理生成的bean定義猪杭,如果不設(shè)置成員
autowireCandidatePatterns的值餐塘,則設(shè)置bean定義的autowireCandidate為false。 > 若bean定義是AnnotatedBeanDefinition**的實(shí)例皂吮,需要執(zhí)行代碼 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate)處理注解為 @Lazy戒傻、@Primary税手、@DependsOn、@Role需纳、@Description
芦倒,解析到bean定義中。然后再調(diào)用org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate方法判斷bean定義是否已經(jīng)被注冊不翩,若沒有注冊兵扬,進(jìn)行后續(xù)的bean定義創(chuàng)建流程,若已經(jīng)注冊 則返回false或者拋出bean沖突異常口蝠。遍歷結(jié)束返回所有的 beanDefinitions集合**器钟。image.png - ??????????處理@Import注解,執(zhí)行代碼
processImports(configClass, sourceClass, getImports(sourceClass), true)
妙蔗,先遞歸的獲取所有注解帶有的@Import傲霸,然后參數(shù)傳入到org.springframework.context.annotation.ConfigurationClassParser
#processImports方法中。進(jìn)入當(dāng)前方法之后眉反,首先對導(dǎo)入候選注解做一個(gè)非空判斷昙啄,集合為空直接結(jié)束@Import處理,再根據(jù)傳入的參數(shù)checkForCircularImports
判斷如果啟動循環(huán)導(dǎo)入檢查(true)并且被放入導(dǎo)入棧中(importStack
)則結(jié)束處理禁漓;否則開始解析所有的importCandidates集合跟衅。若導(dǎo)入候選類型為ImportSelector
,則實(shí)例化當(dāng)前ImportSelector
實(shí)例,同時(shí)會判斷當(dāng)前實(shí)例是否是Aware
子類型播歼,若是,則回調(diào)具體的Aware子接口(BeanClassLoaderAware掰读、BeanFactoryAware && 是BeanFactory的子類型秘狞、EnvironmentAware、ResourceLoaderAware)蹈集,進(jìn)一步判斷是否是DeferredImportSelector子接口類型
烁试,若是,直接添加到deferredImportSelectors集合中拢肆,否則調(diào)用當(dāng)前實(shí)例的selectImports
方法减响,執(zhí)行自定義的導(dǎo)入處理。比如boot中自動裝配功能org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
的selectImports實(shí)現(xiàn)郭怪,會將spring.factories中的所有EnableAutoConfiguration.class的實(shí)現(xiàn)篩選出來支示。接下來的遞歸操作也正是AutoConfigurationImportSelector
這類篩選器的操作體現(xiàn),因?yàn)閳?zhí)行完篩選之后鄙才,可能生成很多@Configuration類颂鸿。另一種情況,若是ImportBeanDefinitionRegistrar類型的攒庵,依然是先執(zhí)行這行回調(diào)代碼ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry)
嘴纺。然后向
Map<ImportBeanDefinitionRegistrar, AnnotationMetadata>importBeanDefinitionRegistrars
= new LinkedHashMap<>()成員遍歷中添加當(dāng)前beanDefinetionRegistrar
败晴。??這里用org.apache.dubbo.config.spring.context.annotation.DubboConfigConfigurationRegistrar類做示例??,代碼如下:image.png
首先根據(jù)被注解類的注解元數(shù)據(jù)獲取@EnableDubboConfig的注解類型栽渴,取到屬性multiple
對應(yīng)的值尖坤,先把注冊單個(gè)bean,若屬性值為true闲擦,再注冊多個(gè)bean慢味。如果既不是ImportSelector類型也不是ImportBeanDefinitionRegistrar類型,則將資源配置元數(shù)據(jù)添加到importStack中佛致,調(diào)用processConfigurationClass(candidate.asConfigClass(configClass))代碼贮缕,至此@Import注解解析完畢。 - ??????處理@ImportResource注解俺榆,
同樣的方式獲取ImportResource注解的屬性值感昼,注解屬性不為空,獲取對應(yīng)屬性locations罐脊、reader的值
定嗓,先處理占位符,再將設(shè)置的BeanDefinitionReader添加到緩存Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>()
中萍桌。 - ????????處理單獨(dú)的@Bean methods宵溅,
獲取被注釋@Bean的方法,若存在并且元數(shù)據(jù)注解是StandardAnnotationMetadata類型
上炎,則嘗試使用ASM讀取并推斷聲明順序(但是由于JVM的反射機(jī)制返回的方法是任意的順序)恃逻,若解析出來的bean方法不為空遍歷并添加到配置類成員 Set<BeanMethod>beanMethods
= new LinkedHashSet<>()中緩存起來。 - ????????處理接口的 default 方法對應(yīng)的@Bean方法藕施,獲取元數(shù)據(jù)class寇损,然后獲取class實(shí)現(xiàn)的所有接口對應(yīng)的@Bean方法,添加到configClass配置信息中裳食。
跟上一步的區(qū)別是這一步獲取的是接口對應(yīng)的@Bean方法
矛市。 -
最后將所有的配置類信息存入
org.springframework.context.annotation.ConfigurationClassParser
#configurationClasses緩存中供使用
。image.png
-
通過conditionEvaluator判斷配置階段類型是
????總結(jié): 獲取已經(jīng)注冊的bean定義 -> 處理帶有注解得嵌套類(member)-> @PropertySources -> @ComponentScan -> @Import -> @ImportResource -> @Bean -> Java8特性 Default方法 -> 是否有父類并且父類不能是Java開頭的 -> 解析完會返回null救氯,否則會循環(huán)解析找田。????
- ??????獲取已經(jīng)注冊的bean名稱,根據(jù)名稱獲取所有的bean定義汹族,循環(huán)判斷當(dāng)前bean的屬性是否是全量( 屬性名稱為
??????????解析完成之后對配置類做校驗(yàn),
1. 如果含有@Configuration注解的配置類不能是final修飾 2. 如果配置類是靜態(tài)的径密,則直接返回校驗(yàn)通過午阵,若配置類標(biāo)有@Configuration,但是不允許覆蓋(繼承、重寫方法)底桂,拋出異常植袍,因?yàn)閟pring會對配置類使用CGLIB代理
。-
??????????接著調(diào)用
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitions( Set<ConfigurationClass> configClasses )將當(dāng)前配置類執(zhí)行注冊bean定義籽懦。調(diào)用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitionsForConfigurationClass方法于个,如下:image.png
1. 判斷配置類是否需要跳過,若需要跳過暮顺,則移除已經(jīng)注冊的bean定義和ImportStack中的緩存厅篓。然后判斷配置類是否已經(jīng)被Import了,是則調(diào)用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#registerBeanDefinitionForImportedConfigurationClass方法注冊當(dāng)前配置類:image.png
2. 若當(dāng)前配置類是通過@Import進(jìn)來的或者是嵌套類捶码,首先獲取配置類的注解元數(shù)據(jù)羽氮,創(chuàng)建一個(gè)注解普通beanDefinetion對象 AnnotatedGenericBeanDefinition,通過成員屬性 ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver()解析當(dāng)前配置類的@Scope注解屬性惫恼,獲取屬性值添加到beanDefinetion中
(默認(rèn)單例)档押。然后處理通用的bean定義注解(包括 @Lazy、@Primary祈纯、@DependsOn令宿、@Role、@Description)封裝到bean定義中腕窥。最后創(chuàng)建BeanDefinitionHolder對象粒没,根據(jù)前面解析的scope屬性判斷是否需要做代理
,通過BeanDefinitionRegistry
注冊器將bean定義注冊到bean工廠中簇爆。
3. 加載配置類的@Bean方法相關(guān)配置癞松,迭代調(diào)用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitionsForBeanMethod方法加載bean定義。方法篇幅過大入蛆,大致流程是:判斷是否需要跳過注冊拦惋,然后獲取@Bean的屬性值,為名稱注冊別名
安寺;接著判斷是否允許存在的bean定義被覆蓋,若允許此處會提前結(jié)束bean創(chuàng)建首尼,不允許就開始創(chuàng)建配置類bean定義挑庶,然后進(jìn)行如下判斷:image.png
判斷當(dāng)前方法元數(shù)據(jù)是否是靜態(tài)的,設(shè)置beanClassName為配置類的名稱软能,設(shè)置工廠方法名為當(dāng)前靜態(tài)方法迎捺;若不是靜態(tài)的,則設(shè)置工廠名稱為配置類的名稱查排,設(shè)置唯一的工廠方法名稱為當(dāng)前@Bean的方法名凳枝。設(shè)置當(dāng)前bean定義的自動注入模式為構(gòu)造器注入,設(shè)置RequiredAnnotationBeanPostProcessor.skipRequiredCheck
跳過@Required檢查屬性的值為true。然后再判斷當(dāng)前元數(shù)據(jù)是否包含通用的bean定義注解:@Lazy岖瑰、@Primary叛买、@Role、@DependsOn蹋订、@Description
率挣,若存在將屬性設(shè)置到bean定義中,再把@Bean注解其他屬性值填充到bean定義中露戒。
獲取當(dāng)前方法對應(yīng)的@Scope注解的屬性椒功,緊接著判斷是否需要啟動代理模式,若需要根據(jù)具體的代理方式創(chuàng)建出代理bean定義,然后注冊到bean工廠中
智什。image.png
4. 加載配置類的所有導(dǎo)入的importSources动漾,執(zhí)行遍歷。校驗(yàn)BeanDefinitionReader.class是否與緩存的Class類型相同荠锭,相同的話旱眯,在java里面會把readerClass設(shè)置為XmlBeanDefinitionReader.class
,專門用來解析XML的BeanDefinetionReader實(shí)現(xiàn)节沦。然后判斷緩存中是否存在當(dāng)前readerClass键思,不存在的話反射創(chuàng)建一個(gè)reader對象,同時(shí)設(shè)置資源加載器為當(dāng)前資源加載器實(shí)例this.resourceLoader
甫贯,設(shè)置上下文環(huán)境this.environment
然后放到緩存中吼鳞。最后調(diào)用reader.loadBeanDefinitions(resource)代碼去加載bean定義。image.png
5. 加載配置類的所有導(dǎo)入的ImportBeanDefinetionRegistrars叫搁。遍歷registrars然后調(diào)用registrar的registerBeanDefinitions方法赔桌,實(shí)現(xiàn)所有子類的自定義回調(diào)
。
總結(jié):至此渴逻,ConfigurationClassPostProcessor后置處理器中
校驗(yàn)beanDefinetion疾党、解析beanDefinetion、加載beanDefinetion就完成了
惨奕。
注冊流程為: 校驗(yàn)是否需要跳過 -> @Import導(dǎo)入或者是嵌套類的配置類信息 -> @Bean方法配置信息注冊 -> @ImportResource配置類信息 -> ImportBeanDefinetionRegistrar類型的配置類信息雪位。
- ? 文章要是勘誤或者知識點(diǎn)說的不正確,歡迎評論梨撞,畢竟這也是作者通過閱讀源碼獲得的知識雹洗,難免會有疏忽!
- ? 要是感覺文章對你有所幫助卧波,不妨點(diǎn)個(gè)關(guān)注时肿,或者移駕看一下作者的其他文集,也都是干活多多哦港粱,文章也在全力更新中螃成。
- ? 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處寸宏!