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

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中執行處理邏輯為

    1. ??????獲取已經注冊的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生成策略。
    2. ??????下面開始正式解析被@Configuration或者普通注解標注的類:創建配置類解析器ConfigurationClassParser實例構造器會將ComponentScanAnnotationParser 解析器對象一起創建,用于解析@ComponentScan),接著調用org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)解析方法,根據bean定義的不同,創建不同的ConfigurationClass實例對象然后統一調用processConfigurationClass方法做循環解析處理
      1. 通過conditionEvaluator判斷配置階段類型是ConfigurationPhase.PARSE_CONFIGURATION的配置類是否帶有@Conditional注解,并做解析校驗,判斷是否需要跳過.
      2. 從緩存獲取當前配置類是否被導入,若導入并且當前的配置類也被導入,則將當前的配置類添加到緩存的配置類中進行合并,若當前配置類沒有被導入,則將舊的配置類從緩存中移除,目的是對配置類進行校驗。
      3. ????????調用org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法中,首先判斷配置類有沒有被@Component標注(包括@Configuration),有的話,調用org.springframework.context.annotation.ConfigurationClassParser#processMemberClasses 首先處理嵌套的成員類類中套類示例
        image.png
      4. ????????處理@PropertySource注解,通過AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class,PropertySource.class)獲取元數據標注的所有@PropertySource注解,然后調用org.springframework.context.annotation.ConfigurationClassParser#processPropertySource去處理每個propertySource包括解析占位符然后將屬性添加到propertySourceNames集合中。
      5. ????????處理@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
      6. ??????????處理@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.AutoConfigurationImportSelectorselectImports實現,會將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注解解析完畢
      7. ??????處理@ImportResource注解,同樣的方式獲取ImportResource注解的屬性值,注解屬性不為空,獲取對應屬性locations、reader的值先處理占位符,再將設置的BeanDefinitionReader添加到緩存 Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>()
      8. ????????處理單獨的@Bean methods獲取被注釋@Bean的方法,若存在并且元數據注解是StandardAnnotationMetadata類型則嘗試使用ASM讀取并推斷聲明順序但是由于JVM的反射機制返回的方法是任意的順序),若解析出來的bean方法不為空遍歷并添加到配置類成員 Set<BeanMethod> beanMethods = new LinkedHashSet<>()中緩存起來。
      9. ????????處理接口的 default 方法對應的@Bean方法,獲取元數據class,然后獲取class實現的所有接口對應的@Bean方法,添加到configClass配置信息中。跟上一步的區別是這一步獲取的是接口對應的@Bean方法
      10. 最后將所有的配置類信息存入 org.springframework.context.annotation.ConfigurationClassParser#configurationClasses緩存中供使用
        image.png
        解析完配置類,最后調用 this.deferredImportSelectorHandler.process(),此handle的作用就是延遲創建配置類

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

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

  1. ? 文章要是勘誤或者知識點說的不正確,歡迎評論,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽!
  2. ? 要是感覺文章對你有所幫助,不妨點個關注,或者移駕看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
  3. ? 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。