SpringBoot應(yīng)用啟動流程分析

??SpringApplicationSpringBoot提供的幫助應(yīng)用程序啟動的引導(dǎo)類,它負責(zé)

  1. 創(chuàng)建合適的ApplicationContext
  2. 將命令行參數(shù)融入Environment抽象
  3. 刷新ApplicationContext,初始化所有的singleton bean
  4. 觸發(fā)所有的CommandLineRunner回調(diào)

本文中的源碼分析基于spring-boot-2.1.5.RELEASE,各位看官還請注意下版本。

構(gòu)造函數(shù)

??大多數(shù)時候,我們使用SpringApplication#run(...)工廠方法來創(chuàng)建SpringApplication實例,其實本質(zhì)上都是對構(gòu)造函數(shù)的調(diào)用。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 資源加載器,一般為空,因為 ApplicationContext 本身實現(xiàn)了 ResourceLoader 接口
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 主配置類,一般情況下是標注了 @SpringBootApplication 注解的類
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 根據(jù) jar 包的引入情況判定應(yīng)用類型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 獲取 spring.factories 中配置的 ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 獲取 spring.factories 中配置的 ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 獲取 main 函數(shù)入口類
    this.mainApplicationClass = deduceMainApplicationClass();
}

primarySources是創(chuàng)建ApplicationContext時傳遞給它的主配置類,一般情況下是標注了@SpringBootApplication注解的類;webApplicationType則根據(jù)類路徑下jar包的引入情況來進行推斷,比如引入了spring-boot-starter-web就會被推斷為WebApplicationType.SERVLET#setInitializers(...)#setListeners(...)則使用之前講解過的Spring SPI機制來獲取對應(yīng)的實現(xiàn);最后mainApplicationClass則對應(yīng)調(diào)用棧中聲明了main函數(shù)的類。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // 使用 Set 防止重復(fù)
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 反射創(chuàng)建實例,其中 parameterTypes 是構(gòu)造函數(shù)形參類型,args 是實參
    // 具體代碼基本上就是 clazz.getDeclaredConstructor(parameterTypes).newInstance(args)
    // 節(jié)約篇幅,就不放出來了
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 基于 @Order/Ordered 排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

這里對Spring SPI機制的使用是非常直接的,同時也告訴了我們?nèi)绾巫宰远x的ApplicationContextInitializerApplicationListener,比如在自定義starterMETA-INF/spring.factories中加入:

org.springframework.context.ApplicationListener=example.MyApplicationListener

就完成了對MyApplicationListener的注冊。

啟動過程

public ConfigurableApplicationContext run(String... args) {
    // 用于記錄啟動時長
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    // 容納 SPI SpringBootExceptionReporter 的實現(xiàn)
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 服務(wù)端程序一般都是沒有UI的
    configureHeadlessProperty();
    // 獲取 SPI SpringApplicationRunListener 的實現(xiàn)
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 回調(diào) SpringApplicationRunListener#starting(...),此時 #run(...) 剛開始運行
    listeners.starting();

    try {
        // 封裝命令行參數(shù)
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 1. 創(chuàng)建 Environment 并進行配置
        // 2. 回調(diào) SpringApplicationRunListener#environmentPrepared(...),此時 Environment 已創(chuàng)建
        // 3. 為 ConfigurationProperties binding 做準備
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 打印 banner
        Banner printedBanner = printBanner(environment);
        // 根據(jù) webApplicationType 來創(chuàng)建 ApplicationContext
        context = createApplicationContext();
        // 獲取 SPI SpringBootExceptionReporter 的實現(xiàn)
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // 1. 配置 ApplicationContext
        // 2. 運行 ApplicationInitializer(s)
        // 3. 回調(diào) SpringApplicationRunListener#contextPrepared(...),
        //    此時 ApplicationContext 已經(jīng)創(chuàng)建,但 BeanDefinition 還未加載
        // 4. 加載 Bean 定義信息,但不刷新容器,此時已經(jīng)有了創(chuàng)建 Bean 的藍圖 BeanDefinition
        // 5. 回調(diào) SpringApplicationRunListener#contextLoaded(...),此時 BeanDefinition 已經(jīng)
        //    悉數(shù)加載完畢,但容器未刷新,對應(yīng)的 bean 實例還未創(chuàng)建
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 刷新容器,初始化所有 non-lazy-init 的 bean
        refreshContext(context);
        // 鉤子函數(shù)
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 回調(diào) SpringApplicationRunListener#started(...),此時容器已刷新,bean 也初始化完畢
        listeners.started(context);
        // 回調(diào)容器中所有的 ApplicationRunner 和 CommandLineRunner
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        // 1. 回調(diào) SpringApplicationRunListener#failed(...)
        // 2. 使用 SpringBootExceptionReporter 來處理異常
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 回調(diào) SpringApplicationRunListener#running(...),此時不僅容器已刷新,各種 *Runner 也已回調(diào)
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }

    return context;
}

#run(...)方法雖然略長,邏輯卻是很清晰的:

  1. 創(chuàng)建Environment并進行配置
  2. 創(chuàng)建ApplicationContext并進行配置
  3. 加載配置類并解析出對應(yīng)的BeanDefinition
  4. 刷新容器,根據(jù)BeanDefinition創(chuàng)建Bean
  5. 回調(diào)*Runner接口

SpringApplicationRunListener則提供了對#run(...)過程中關(guān)鍵節(jié)點的回調(diào),默認注冊有EventPublishingRunListener——它使用ApplicationEventMulticaster發(fā)出對應(yīng)的事件。其中比較重要的有ApplicationEnvironmentPreparedEventApplicationReadyEvent,SpringCloud中的很多特性,比如Bootstrap Context的創(chuàng)建和Environment的動態(tài)刷新就是基于這些事件來觸發(fā)的。SpringBootExceptionReporter則提供了對#run(...)過程中異常的處理,默認注冊的FailureAnalyzers會打印異常信息并提供一個解決方案,類似下面的輸出相信大家都不會陌生(笑)。

***************************
APPLICATION FAILED TO START
***************************

Description:

Field restTemplate in example.TestController required a single bean, but 2 were found:
    - restTemplate1: defined by method 'restTemplate1' in class path resource [example/Config.class]
    - restTemplate2: defined by method 'restTemplate2' in class path resource [example/Config.class]

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

創(chuàng)建Environment并進行配置

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                  ApplicationArguments applicationArguments) {
    // 根據(jù) webApplicationType 來創(chuàng)建 Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 1. 添加 ConversionService 用于類型轉(zhuǎn)換
    // 2. 添加 defaultProperties 和 commandLineArgs 兩個 PropertySource
    // 3. 合并 additionalProfiles 到 activeProfiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 回調(diào),發(fā)出 ApplicationEnvironmentPreparedEvent 事件
    listeners.environmentPrepared(environment);
    // 將 spring.main 前綴的配置項應(yīng)用到 SpringApplication,比如 allowBeanDefinitionOverriding
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    // 將 PropertySource 轉(zhuǎn)換成 ConfigurationPropertySource
    // 這一步是為了支持 ConfigurationProperties binding
    ConfigurationPropertySources.attach(environment);
    return environment;
}

創(chuàng)建ApplicationContext并進行配置

protected ConfigurableApplicationContext createApplicationContext() {
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
         switch (this.webApplicationType) {
         case SERVLET:
            contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE:
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default:
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      } catch (ClassNotFoundException ex) {
         throw new IllegalStateException(
               "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
               ex);
      }
   }
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

根據(jù)webApplicationType創(chuàng)建對應(yīng)的ApplicationContext,比如引入了spring-boot-starter-web就會創(chuàng)建AnnotationConfigServletWebServerApplicationContext,進而啟動內(nèi)嵌的Tomcat容器,這部分內(nèi)容之前已經(jīng)聊過了,大家可以翻翻看。

加載配置類并解析出對應(yīng)的BeanDefinition

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 設(shè)置使用的 Environment
   context.setEnvironment(environment);
    // 設(shè)置 ResourceLoader、ClassLoader和ConversionService
   postProcessApplicationContext(context);
    // 應(yīng)用上前面加載好的 ApplicationContextInitializer
   applyInitializers(context);
    // 回調(diào),發(fā)出 ApplicationPreparedEvent 事件
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // 單獨注冊一下幾個比較特殊的 Bean
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
    // 將 allowBeanDefinitionOverriding 配置項應(yīng)用到 ApplicationContext 底層的 BeanFactory
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   // primarySources 和以編程方式加入的其它配置源
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
    // 從以上配置源中解析出 BeanDefinition
    // 這部分屬于 spring-context 包的內(nèi)容了,無非是使用 AnnotatedBeanDefinitionReader
    // ClassPathBeanDefinitionScanner 等組件來加載并解析
   load(context, sources.toArray(new Object[0]));
    // 回調(diào),發(fā)出 ApplicationPreparedEvent 事件
   listeners.contextLoaded(context);
}

大家很熟悉的自動裝配就發(fā)生在這一階段,@EnableAutoConfiguration注解一般是標注在primarySource上的,看一下它的定義:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   /**
    * 待排除的自動配置類
    */
   Class<?>[] exclude() default {};
    
   /**
    * 待排除的自動配置類名
    */
   String[] excludeName() default {};
}

它通過@Import注解向容器中導(dǎo)入了一個ImportSelector接口的實現(xiàn)——AutoConfigurationImportSelector,并在這里實現(xiàn)了自動裝配邏輯。鑒于這部分內(nèi)容比較多,咱們下次可以展開聊聊。

刷新容器,根據(jù)BeanDefinition創(chuàng)建Bean

protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
   ((AbstractApplicationContext) applicationContext).refresh();
}

刷新容器直接調(diào)用ApplicationContext#refresh()方法即可,這將導(dǎo)致所有non-lazy-initsingleton bean得到初始化。

回調(diào)*Runner接口

private void callRunners(ApplicationContext context, ApplicationArguments args) {
   List<Object> runners = new ArrayList<>();
    // 獲取容器中所有類型為 ApplicationRunner 的 bean
   runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 獲取容器中所有類型為 CommandLineRunner 的 bean
   runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    // 基于 @Order/Ordered 排序 
   AnnotationAwareOrderComparator.sort(runners);
    // 接著就是逐個調(diào)用了
   for (Object runner : new LinkedHashSet<>(runners)) {
      if (runner instanceof ApplicationRunner) {
         callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
         callRunner((CommandLineRunner) runner, args);
      }
   }
}

這部分代碼是自解釋的,不多提了。

結(jié)語

??今天我們一起分析了SpringBoot應(yīng)用的啟動流程,也僅僅是主體的啟動流程。Spring本身是完全開放、極易擴展的,大家也看到了伴隨著應(yīng)用啟動的各種回調(diào),眾多的SpringBoot特性都是在這些擴展點中得到實現(xiàn)的,這也是接下來的文章要分析的內(nèi)容。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容