SpringBoot源碼分析之Spring容器的refresh過程

上一篇文章中,我們分析了SpringBoot的啟動過程:構造SpringApplication并調用它的run方法。其中構造SpringApplication的時候會初始化一些監聽器和初始化器;run方法調用的過程中會有對應的監聽器監聽,并且會創建Spring容器。

Spring容器創建之后,會調用它的refresh方法,refresh的時候會做很多事情:比如完成配置類的解析、各種BeanFactoryPostProcessor和BeanPostProcessor的注冊、國際化配置的初始化、web內置容器的構造等等。

我們來分析一下這個refresh過程。

還是以web程序為例,那么對應的Spring容器為AnnotationConfigEmbeddedWebApplicationContext。它的refresh方法調用了父類AbstractApplicationContext的refresh方法:

    public void refresh() throws BeansException, IllegalStateException {
      // refresh過程只能一個線程處理,不允許并發執行
      synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        try {
          postProcessBeanFactory(beanFactory);
          invokeBeanFactoryPostProcessors(beanFactory);
          registerBeanPostProcessors(beanFactory);
          initMessageSource();
          initApplicationEventMulticaster();
          onRefresh();
          registerListeners();
          finishBeanFactoryInitialization(beanFactory);
          finishRefresh();
        }
        catch (BeansException ex) {
          if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                "cancelling refresh attempt: " + ex);
          }
          destroyBeans();
          cancelRefresh(ex);
          throw ex;
        }
        finally {
          resetCommonCaches();
        }
      }
    }

prepareRefresh方法

表示在真正做refresh操作之前需要準備做的事情:

  1. 設置Spring容器的啟動時間,撤銷關閉狀態,開啟活躍狀態。
  2. 初始化屬性源信息(Property)
  3. 驗證環境信息里一些必須存在的屬性

prepareBeanFactory方法

從Spring容器獲取BeanFactory(Spring Bean容器)并進行相關的設置為后續的使用做準備:

  1. 設置classloader(用于加載bean),設置表達式解析器(解析bean定義中的一些表達式),添加屬性編輯注冊器(注冊屬性編輯器)
  2. 添加ApplicationContextAwareProcessor這個BeanPostProcessor。取消ResourceLoaderAware、ApplicationEventPublisherAware、MessageSourceAware、ApplicationContextAware、EnvironmentAware這5個接口的自動注入。因為ApplicationContextAwareProcessor把這5個接口的實現工作做了
  3. 設置特殊的類型對應的bean。BeanFactory對應剛剛獲取的BeanFactory;ResourceLoader、ApplicationEventPublisher、ApplicationContext這3個接口對應的bean都設置為當前的Spring容器
  4. 注入一些其它信息的bean,比如environment、systemProperties等

postProcessBeanFactory方法

BeanFactory設置之后再進行后續的一些BeanFactory操作。

不同的Spring容器做不同的操作。比如GenericWebApplicationContext容器會在BeanFactory中添加ServletContextAwareProcessor用于處理ServletContextAware類型的bean初始化的時候調用setServletContext或者setServletConfig方法(跟ApplicationContextAwareProcessor原理一樣)。

AnnotationConfigEmbeddedWebApplicationContext對應的postProcessBeanFactory方法:

    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
      // 調用父類EmbeddedWebApplicationContext的實現
      super.postProcessBeanFactory(beanFactory);
      // 查看basePackages屬性,如果設置了會使用ClassPathBeanDefinitionScanner去掃描basePackages包下的bean并注冊
      if (this.basePackages != null && this.basePackages.length > 0) {
        this.scanner.scan(this.basePackages);
      }
      // 查看annotatedClasses屬性,如果設置了會使用AnnotatedBeanDefinitionReader去注冊這些bean
      if (this.annotatedClasses != null && this.annotatedClasses.length > 0) {
        this.reader.register(this.annotatedClasses);
      }
    }

父類EmbeddedWebApplicationContext的實現:

    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
      beanFactory.addBeanPostProcessor(
          new WebApplicationContextServletContextAwareProcessor(this));
      beanFactory.ignoreDependencyInterface(ServletContextAware.class);
    }

invokeBeanFactoryPostProcessors方法

在Spring容器中找出實現了BeanFactoryPostProcessor接口的processor并執行。Spring容器會委托給PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法執行。

介紹兩個接口:

  1. BeanFactoryPostProcessor:用來修改Spring容器中已經存在的bean的定義,使用ConfigurableListableBeanFactory對bean進行處理
  2. BeanDefinitionRegistryPostProcessor:繼承BeanFactoryPostProcessor,作用跟BeanFactoryPostProcessor一樣,只不過是使用BeanDefinitionRegistry對bean進行處理

基于web程序的Spring容器AnnotationConfigEmbeddedWebApplicationContext構造的時候,會初始化內部屬性AnnotatedBeanDefinitionReader reader,這個reader構造的時候會在BeanFactory中注冊一些post processor,包括BeanPostProcessor和BeanFactoryPostProcessor(比如ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor):

AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);

invokeBeanFactoryPostProcessors方法處理BeanFactoryPostProcessor的邏輯如下:

從Spring容器中找出BeanDefinitionRegistryPostProcessor類型的bean(這些processor是在容器剛創建的時候通過構造AnnotatedBeanDefinitionReader的時候注冊到容器中的),然后按照優先級分別執行,優先級的邏輯如下:

  1. 實現PriorityOrdered接口的BeanDefinitionRegistryPostProcessor先全部找出來,然后排序后依次執行
  2. 實現Ordered接口的BeanDefinitionRegistryPostProcessor找出來,然后排序后依次執行
  3. 沒有實現PriorityOrdered和Ordered接口的BeanDefinitionRegistryPostProcessor找出來執行并依次執行

接下來從Spring容器內查找BeanFactoryPostProcessor接口的實現類,然后執行(如果processor已經執行過,則忽略),這里的查找規則跟上面查找BeanDefinitionRegistryPostProcessor一樣,先找PriorityOrdered,然后是Ordered,最后是兩者都沒。

這里需要說明的是ConfigurationClassPostProcessor這個processor是優先級最高的被執行的processor(實現了PriorityOrdered接口)。這個ConfigurationClassPostProcessor會去BeanFactory中找出所有有@Configuration注解的bean,然后使用ConfigurationClassParser去解析這個類。ConfigurationClassParser內部有個Map<ConfigurationClass, ConfigurationClass>類型的configurationClasses屬性用于保存解析的類,ConfigurationClass是一個對要解析的配置類的封裝,內部存儲了配置類的注解信息、被@Bean注解修飾的方法、@ImportResource注解修飾的信息、ImportBeanDefinitionRegistrar等都存儲在這個封裝類中。

這里ConfigurationClassPostProcessor最先被處理還有另外一個原因是如果程序中有自定義的BeanFactoryPostProcessor,那么這個PostProcessor首先得通過ConfigurationClassPostProcessor被解析出來,然后才能被Spring容器找到并執行。(ConfigurationClassPostProcessor不先執行的話,這個Processor是不會被解析的,不會被解析的話也就不會執行了)。

在我們的程序中,只有主類RefreshContextApplication有@Configuration注解(@SpringBootApplication注解帶有@Configuration注解),所以這個配置類會被ConfigurationClassParser解析。解析過程如下:

  1. 處理@PropertySources注解:進行一些配置信息的解析
  2. 處理@ComponentScan注解:使用ComponentScanAnnotationParser掃描basePackage下的需要解析的類(@SpringBootApplication注解也包括了@ComponentScan注解,只不過basePackages是空的,空的話會去獲取當前@Configuration修飾的類所在的包),并注冊到BeanFactory中(這個時候bean并沒有進行實例化,而是進行了注冊。具體的實例化在finishBeanFactoryInitialization方法中執行)。對于掃描出來的類,遞歸解析
  3. 處理@Import注解:先遞歸找出所有的注解,然后再過濾出只有@Import注解的類,得到@Import注解的值。比如查找@SpringBootApplication注解的@Import注解數據的話,首先發現@SpringBootApplication不是一個@Import注解,然后遞歸調用修飾了@SpringBootApplication的注解,發現有個@EnableAutoConfiguration注解,再次遞歸發現被@Import(EnableAutoConfigurationImportSelector.class)修飾,還有@AutoConfigurationPackage注解修飾,再次遞歸@AutoConfigurationPackage注解,發現被@Import(AutoConfigurationPackages.Registrar.class)注解修飾,所以@SpringBootApplication注解對應的@Import注解有2個,分別是@Import(AutoConfigurationPackages.Registrar.class)和@Import(EnableAutoConfigurationImportSelector.class)。找出所有的@Import注解之后,開始處理邏輯:
    1. 遍歷這些@Import注解內部的屬性類集合
    2. 如果這個類是個ImportSelector接口的實現類,實例化這個ImportSelector,如果這個類也是DeferredImportSelector接口的實現類,那么加入ConfigurationClassParser的deferredImportSelectors屬性中讓第6步處理。否則調用ImportSelector的selectImports方法得到需要Import的類,然后對這些類遞歸做@Import注解的處理
    3. 如果這個類是ImportBeanDefinitionRegistrar接口的實現類,設置到配置類的importBeanDefinitionRegistrars屬性中
    4. 其它情況下把這個類入隊到ConfigurationClassParser的importStack(隊列)屬性中,然后把這個類當成是@Configuration注解修飾的類遞歸重頭開始解析這個類
  4. 處理@ImportResource注解:獲取@ImportResource注解的locations屬性,得到資源文件的地址信息。然后遍歷這些資源文件并把它們添加到配置類的importedResources屬性中
  5. 處理@Bean注解:獲取被@Bean注解修飾的方法,然后添加到配置類的beanMethods屬性中
  6. 處理DeferredImportSelector:處理第3步@Import注解產生的DeferredImportSelector,進行selectImports方法的調用找出需要import的類,然后再調用第3步相同的處理邏輯處理

這里@SpringBootApplication注解被@EnableAutoConfiguration修飾,@EnableAutoConfiguration注解被@Import(EnableAutoConfigurationImportSelector.class)修飾,所以在第3步會找出這個@Import修飾的類EnableAutoConfigurationImportSelector,這個類剛好實現了DeferredImportSelector接口,接著就會在第6步被執行。第6步selectImport得到的類就是自動化配置類。

EnableAutoConfigurationImportSelector的selectImport方法會在spring.factories文件中找出key為EnableAutoConfiguration對應的值,有81個,這81個就是所謂的自動化配置類(XXXAutoConfiguration)。

ConfigurationClassParser解析完成之后,被解析出來的類會放到configurationClasses屬性中。然后使用ConfigurationClassBeanDefinitionReader去解析這些類。

這個時候這些bean只是被加載到了Spring容器中。下面這段代碼是ConfigurationClassBeanDefinitionReader的解析bean過程:

    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
      TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
      for (ConfigurationClass configClass : configurationModel) {
        // 對每一個配置類,調用loadBeanDefinitionsForConfigurationClass方法
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
      }
    }

    private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
        TrackedConditionEvaluator trackedConditionEvaluator) {
      // 使用條件注解判斷是否需要跳過這個配置類
      if (trackedConditionEvaluator.shouldSkip(configClass)) {
        // 跳過配置類的話在Spring容器中移除bean的注冊
        String beanName = configClass.getBeanName();
        if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
          this.registry.removeBeanDefinition(beanName);
        }
        this.importRegistry.removeImportingClassFor(configClass.getMetadata().getClassName());
        return;
      }

      if (configClass.isImported()) {
        // 如果自身是被@Import注釋所import的,注冊自己
        registerBeanDefinitionForImportedConfigurationClass(configClass);
      }
      // 注冊方法中被@Bean注解修飾的bean
      for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
      }
      // 注冊@ImportResource注解注釋的資源文件中的bean
      loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
      // 注冊@Import注解中的ImportBeanDefinitionRegistrar接口的registerBeanDefinitions
      loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }

invokeBeanFactoryPostProcessors方法總結來說就是從Spring容器中找出BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口的實現類并按照一定的規則順序進行執行。 其中ConfigurationClassPostProcessor這個BeanDefinitionRegistryPostProcessor優先級最高,它會對項目中的@Configuration注解修飾的類(@Component、@ComponentScan、@Import、@ImportResource修飾的類也會被處理)進行解析,解析完成之后把這些bean注冊到BeanFactory中。需要注意的是這個時候注冊進來的bean還沒有實例化。

registerBeanPostProcessors方法

從Spring容器中找出的BeanPostProcessor接口的bean,并設置到BeanFactory的屬性中。之后bean被實例化的時候會調用這個BeanPostProcessor。

該方法委托給了PostProcessorRegistrationDelegate類的registerBeanPostProcessors方法執行。這里的過程跟invokeBeanFactoryPostProcessors類似:

  1. 先找出實現了PriorityOrdered接口的BeanPostProcessor并排序后加到BeanFactory的BeanPostProcessor集合中
  2. 找出實現了Ordered接口的BeanPostProcessor并排序后加到BeanFactory的BeanPostProcessor集合中
  3. 沒有實現PriorityOrdered和Ordered接口的BeanPostProcessor加到BeanFactory的BeanPostProcessor集合中

這些已經存在的BeanPostProcessor在postProcessBeanFactory方法中已經說明,都是由AnnotationConfigUtils的registerAnnotationConfigProcessors方法注冊的。這些BeanPostProcessor包括有AutowiredAnnotationBeanPostProcessor(處理被@Autowired注解修飾的bean并注入)、RequiredAnnotationBeanPostProcessor(處理被@Required注解修飾的方法)、CommonAnnotationBeanPostProcessor(處理@PreDestroy、@PostConstruct、@Resource等多個注解的作用)等。

如果是自定義的BeanPostProcessor,已經被ConfigurationClassPostProcessor注冊到容器內。

這些BeanPostProcessor會在這個方法內被實例化(通過調用BeanFactory的getBean方法,如果沒有找到實例化的類,就會去實例化)。

initMessageSource方法

在Spring容器中初始化一些國際化相關的屬性。

initApplicationEventMulticaster方法

在Spring容器中初始化事件廣播器,事件廣播器用于事件的發布。

SpringBoot源碼分析之SpringBoot的啟動過程中分析過,EventPublishingRunListener這個SpringApplicationRunListener會監聽事件,其中發生contextPrepared事件的時候EventPublishingRunListener會把事件廣播器注入到BeanFactory中。

所以initApplicationEventMulticaster不再需要再次注冊,只需要拿出BeanFactory中的事件廣播器然后設置到Spring容器的屬性中即可。如果沒有使用SpringBoot的話,Spring容器得需要自己初始化事件廣播器。

onRefresh方法

一個模板方法,不同的Spring容器做不同的事情。

比如web程序的容器AnnotationConfigEmbeddedWebApplicationContext中會調用createEmbeddedServletContainer方法去創建內置的Servlet容器。

目前SpringBoot只支持3種內置的Servlet容器:

  1. Tomcat
  2. Jetty
  3. Undertow

registerListeners方法

把Spring容器內的時間監聽器和BeanFactory中的時間監聽器都添加的事件廣播器中。

然后如果存在early event的話,廣播出去。

finishBeanFactoryInitialization方法

實例化BeanFactory中已經被注冊但是未實例化的所有實例(懶加載的不需要實例化)。

比如invokeBeanFactoryPostProcessors方法中根據各種注解解析出來的類,在這個時候都會被初始化。

實例化的過程各種BeanPostProcessor開始起作用。

finishRefresh方法

refresh做完之后需要做的其他事情。

  1. 初始化生命周期處理器,并設置到Spring容器中(LifecycleProcessor)
  2. 調用生命周期處理器的onRefresh方法,這個方法會找出Spring容器中實現了SmartLifecycle接口的類并進行start方法的調用
  3. 發布ContextRefreshedEvent事件告知對應的ApplicationListener進行響應的操作
  4. 調用LiveBeansView的registerApplicationContext方法:如果設置了JMX相關的屬性,則就調用該方法
  5. 發布EmbeddedServletContainerInitializedEvent事件告知對應的ApplicationListener進行響應的操作

總結

Spring容器的refresh過程就是上述11個方法的介紹。內容還是非常多的,本文也只是說了個大概,像bean的實例化過程沒有具體去分析,這方面的內容以后會看情況去做分析。

這篇文章也是為之后的文章比如內置Servlet容器的創建啟動、條件注解的使用等打下基礎。

例子

寫了一個例子用來驗證容器的refresh過程,包括bean解析,processor的使用、Lifecycle的使用等。

可以啟動項目debug去看看對應的過程,這樣對Spring容器會有一個更好的理解。

地址在:https://github.com/fangjian0423/springboot-analysis/tree/master/springboot-refresh-context

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,971評論 6 342
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • 文章作者:Tyan博客:noahsnail.com | CSDN | 簡書 3.8 Container Exten...
    SnailTyan閱讀 1,251評論 0 6
  • 小時候,一走出自己家大門口,向南望去,就會望見村南寬闊的沙河對岸那一道漸走漸高、起伏洶涌的遠山,就會望見那道遠山上...
    散淡旅痕閱讀 808評論 1 1
  • 好些天沒寫東西了,最近不知道怎么了,沒有什么靈感,再加上突然特別懷念以前的老電視劇和老電影,每天都在重溫,幸好有...
    咪咕吸閱讀 268評論 0 1