SpringBoot啟動 源碼深度解析(三)

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í)行處理邏輯為

    1. ??????獲取已經(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生成策略。
    2. ??????下面開始正式解析被@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)解析處理
      1. 通過conditionEvaluator判斷配置階段類型是ConfigurationPhase.PARSE_CONFIGURATION的配置類是否帶有@Conditional注解,并做解析校驗(yàn)兢孝,判斷是否需要跳過.
      2. 從緩存獲取當(dāng)前配置類是否被導(dǎo)入窿凤,若導(dǎo)入并且當(dāng)前的配置類也被導(dǎo)入搀擂,則將當(dāng)前的配置類添加到緩存的配置類中進(jìn)行合并,若當(dāng)前配置類沒有被導(dǎo)入卷玉,則將舊的配置類從緩存中移除哨颂,目的是對配置類進(jìn)行校驗(yàn)。
      3. ????????調(diào)用org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法中相种,首先判斷配置類有沒有被@Component標(biāo)注(包括@Configuration)威恼,有的話,調(diào)用org.springframework.context.annotation.ConfigurationClassParser#processMemberClasses 首先處理嵌套的成員類類中套類示例
        image.png
      4. ????????處理@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集合中斤蔓。
      5. ????????處理@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
      6. ??????????處理@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.AutoConfigurationImportSelectorselectImports實(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注解解析完畢
      7. ??????處理@ImportResource注解俺榆,同樣的方式獲取ImportResource注解的屬性值感昼,注解屬性不為空,獲取對應(yīng)屬性locations罐脊、reader的值定嗓,先處理占位符,再將設(shè)置的BeanDefinitionReader添加到緩存 Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>()萍桌。
      8. ????????處理單獨(dú)的@Bean methods宵溅,獲取被注釋@Bean的方法,若存在并且元數(shù)據(jù)注解是StandardAnnotationMetadata類型上炎,則嘗試使用ASM讀取并推斷聲明順序但是由于JVM的反射機(jī)制返回的方法是任意的順序)恃逻,若解析出來的bean方法不為空遍歷并添加到配置類成員 Set<BeanMethod> beanMethods = new LinkedHashSet<>()中緩存起來。
      9. ????????處理接口的 default 方法對應(yīng)的@Bean方法藕施,獲取元數(shù)據(jù)class寇损,然后獲取class實(shí)現(xiàn)的所有接口對應(yīng)的@Bean方法,添加到configClass配置信息中裳食。跟上一步的區(qū)別是這一步獲取的是接口對應(yīng)的@Bean方法矛市。
      10. 最后將所有的配置類信息存入 org.springframework.context.annotation.ConfigurationClassParser#configurationClasses緩存中供使用
        image.png
        解析完配置類诲祸,最后調(diào)用 this.deferredImportSelectorHandler.process()浊吏,此handle的作用就是延遲創(chuàng)建配置類

    ????總結(jié)獲取已經(jīng)注冊的bean定義 -> 處理帶有注解得嵌套類(member)-> @PropertySources -> @ComponentScan -> @Import -> @ImportResource -> @Bean -> Java8特性 Default方法 -> 是否有父類并且父類不能是Java開頭的 -> 解析完會返回null救氯,否則會循環(huán)解析找田。????

  • ??????????解析完成之后對配置類做校驗(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類型的配置類信息雪位。

  1. ? 文章要是勘誤或者知識點(diǎn)說的不正確,歡迎評論梨撞,畢竟這也是作者通過閱讀源碼獲得的知識雹洗,難免會有疏忽!
  2. ? 要是感覺文章對你有所幫助卧波,不妨點(diǎn)個(gè)關(guān)注时肿,或者移駕看一下作者的其他文集,也都是干活多多哦港粱,文章也在全力更新中螃成。
  3. ? 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處寸宏!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宁炫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子击吱,更是在濱河造成了極大的恐慌淋淀,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件覆醇,死亡現(xiàn)場離奇詭異朵纷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)永脓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門袍辞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人常摧,你說我怎么就攤上這事搅吁。” “怎么了落午?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵谎懦,是天一觀的道長。 經(jīng)常有香客問我溃斋,道長界拦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任梗劫,我火速辦了婚禮享甸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梳侨。我一直安慰自己蛉威,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布走哺。 她就那樣靜靜地躺著蚯嫌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丙躏。 梳的紋絲不亂的頭發(fā)上齐帚,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機(jī)與錄音彼哼,去河邊找鬼。 笑死湘今,一個(gè)胖子當(dāng)著我的面吹牛敢朱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼拴签,長吁一口氣:“原來是場噩夢啊……” “哼孝常!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚓哩,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤构灸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后岸梨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喜颁,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年曹阔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了半开。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赃份,死狀恐怖寂拆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抓韩,我是刑警寧澤纠永,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站谒拴,受9級特大地震影響尝江,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彪薛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一茂装、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧善延,春花似錦少态、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至豆茫,卻和暖如春侨歉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背揩魂。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工幽邓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人火脉。 一個(gè)月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓牵舵,卻偏偏與公主長得像柒啤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子畸颅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359