SpringBoot源碼分析之SpringBoot的啟動過程

SpringBoot的啟動很簡單,代碼如下:

    @SpringBootApplication
    public class MyApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    }

從代碼上可以看出,調(diào)用了SpringApplication的靜態(tài)方法run。這個run方法會構(gòu)造一個SpringApplication的實例,然后再調(diào)用這里實例的run方法就表示啟動SpringBoot。

因此,想要分析SpringBoot的啟動過程,我們需要熟悉SpringApplication的構(gòu)造過程以及SpringApplication的run方法執(zhí)行過程即可。

我們以上述這段代碼為例,分析SpringBoot的啟動過程。

SpringApplication的構(gòu)造過程

SpringApplication構(gòu)造的時候內(nèi)部會調(diào)用一個private方法initialize:

    public SpringApplication(Object... sources) {
      initialize(sources); // sources目前是一個MyApplication的class對象
    }

    private void initialize(Object[] sources) {
      if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources)); // 把sources設(shè)置到SpringApplication的sources屬性中,目前只是一個MyApplication類對象
      }
      this.webEnvironment = deduceWebEnvironment(); // 判斷是否是web程序(javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext都必須在類加載器中存在),并設(shè)置到webEnvironment屬性中
      // 從spring.factories文件中找出key為ApplicationContextInitializer的類并實例化后設(shè)置到SpringApplication的initializers屬性中。這個過程也就是找出所有的應(yīng)用程序初始化器
      setInitializers((Collection) getSpringFactoriesInstances(
          ApplicationContextInitializer.class));
      // 從spring.factories文件中找出key為ApplicationListener的類并實例化后設(shè)置到SpringApplication的listeners屬性中。這個過程就是找出所有的應(yīng)用程序事件監(jiān)聽器
      setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
      // 找出main類,這里是MyApplication類
      this.mainApplicationClass = deduceMainApplicationClass();
    }

ApplicationContextInitializer,應(yīng)用程序初始化器,做一些初始化的工作:

    public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
        void initialize(C applicationContext);
    }

ApplicationListener,應(yīng)用程序事件(ApplicationEvent)監(jiān)聽器:

    public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
        void onApplicationEvent(E event);
    }

這里的應(yīng)用程序事件(ApplicationEvent)有應(yīng)用程序啟動事件(ApplicationStartedEvent),失敗事件(ApplicationFailedEvent),準(zhǔn)備事件(ApplicationPreparedEvent)等。

應(yīng)用程序事件監(jiān)聽器跟監(jiān)聽事件是綁定的。比如ConfigServerBootstrapApplicationListener只跟ApplicationEnvironmentPreparedEvent事件綁定,LiquibaseServiceLocatorApplicationListener只跟ApplicationStartedEvent事件綁定,LoggingApplicationListener跟所有事件綁定等。

默認(rèn)情況下,initialize方法從spring.factories文件中找出的key為ApplicationContextInitializer的類有:

  1. org.springframework.boot.context.config.DelegatingApplicationContextInitializer
  2. org.springframework.boot.context.ContextIdApplicationContextInitializer
  3. org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
  4. org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
  5. org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

key為ApplicationListener的有:

  1. org.springframework.boot.context.config.ConfigFileApplicationListener
  2. org.springframework.boot.context.config.AnsiOutputApplicationListener
  3. org.springframework.boot.logging.LoggingApplicationListener
  4. org.springframework.boot.logging.ClasspathLoggingApplicationListener
  5. org.springframework.boot.autoconfigure.BackgroundPreinitializer
  6. org.springframework.boot.context.config.DelegatingApplicationListener
  7. org.springframework.boot.builder.ParentContextCloserApplicationListener
  8. org.springframework.boot.context.FileEncodingApplicationListener
  9. org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

SpringApplication的執(zhí)行

分析run方法之前,先看一下SpringApplication中的一些事件和監(jiān)聽器概念。

首先是SpringApplicationRunListeners類和SpringApplicationRunListener類的介紹。

SpringApplicationRunListeners內(nèi)部持有SpringApplicationRunListener集合和1個Log日志類。用于SpringApplicationRunListener監(jiān)聽器的批量執(zhí)行。

SpringApplicationRunListener看名字也知道用于監(jiān)聽SpringApplication的run方法的執(zhí)行。

它定義了5個步驟:

  1. started(run方法執(zhí)行的時候立馬執(zhí)行;對應(yīng)事件的類型是ApplicationStartedEvent)
  2. environmentPrepared(ApplicationContext創(chuàng)建之前并且環(huán)境信息準(zhǔn)備好的時候調(diào)用;對應(yīng)事件的類型是ApplicationEnvironmentPreparedEvent)
  3. contextPrepared(ApplicationContext創(chuàng)建好并且在source加載之前調(diào)用一次;沒有具體的對應(yīng)事件)
  4. contextLoaded(ApplicationContext創(chuàng)建并加載之后并在refresh之前調(diào)用;對應(yīng)事件的類型是ApplicationPreparedEvent)
  5. finished(run方法結(jié)束之前調(diào)用;對應(yīng)事件的類型是ApplicationReadyEvent或ApplicationFailedEvent)

SpringApplicationRunListener目前只有一個實現(xiàn)類EventPublishingRunListener,它把監(jiān)聽的過程封裝成了SpringApplicationEvent事件并讓內(nèi)部屬性(屬性名為multicaster)ApplicationEventMulticaster接口的實現(xiàn)類SimpleApplicationEventMulticaster廣播出去,廣播出去的事件對象會被SpringApplication中的listeners屬性進(jìn)行處理。

所以說SpringApplicationRunListener和ApplicationListener之間的關(guān)系是通過ApplicationEventMulticaster廣播出去的SpringApplicationEvent所聯(lián)系起來的。

SpringApplication的run方法代碼如下:

    public ConfigurableApplicationContext run(String... args) {
      StopWatch stopWatch = new StopWatch(); // 構(gòu)造一個任務(wù)執(zhí)行觀察器
      stopWatch.start(); // 開始執(zhí)行,記錄開始時間
      ConfigurableApplicationContext context = null;
      configureHeadlessProperty();
      // 獲取SpringApplicationRunListeners,內(nèi)部只有一個EventPublishingRunListener
      SpringApplicationRunListeners listeners = getRunListeners(args);
      // 上面分析過,會封裝成SpringApplicationEvent事件然后廣播出去給SpringApplication中的listeners所監(jiān)聽
      // 這里接受ApplicationStartedEvent事件的listener會執(zhí)行相應(yīng)的操作
      listeners.started();
      try {
        // 構(gòu)造一個應(yīng)用程序參數(shù)持有類
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
        // 創(chuàng)建Spring容器
        context = createAndRefreshContext(listeners, applicationArguments);
        // 容器創(chuàng)建完成之后執(zhí)行額外一些操作
        afterRefresh(context, applicationArguments);
        // 廣播出ApplicationReadyEvent事件給相應(yīng)的監(jiān)聽器執(zhí)行
        listeners.finished(context, null);
        stopWatch.stop(); // 執(zhí)行結(jié)束,記錄執(zhí)行時間
        if (this.logStartupInfo) {
          new StartupInfoLogger(this.mainApplicationClass)
              .logStarted(getApplicationLog(), stopWatch);
        }
        return context; // 返回Spring容器
      }
      catch (Throwable ex) {
        handleRunFailure(context, listeners, ex); // 這個過程報錯的話會執(zhí)行一些異常操作、然后廣播出ApplicationFailedEvent事件給相應(yīng)的監(jiān)聽器執(zhí)行
        throw new IllegalStateException(ex);
      }
    }

創(chuàng)建容器的方法createAndRefreshContext如下:

    private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
      ConfigurableApplicationContext context; // 定義Spring容器
      // 創(chuàng)建應(yīng)用程序的環(huán)境信息。如果是web程序,創(chuàng)建StandardServletEnvironment;否則,創(chuàng)建StandardEnvironment
      ConfigurableEnvironment environment = getOrCreateEnvironment();
      // 配置一些環(huán)境信息。比如profile,命令行參數(shù)
      configureEnvironment(environment, applicationArguments.getSourceArgs());
      // 廣播出ApplicationEnvironmentPreparedEvent事件給相應(yīng)的監(jiān)聽器執(zhí)行
      listeners.environmentPrepared(environment);
      // 環(huán)境信息的校對
      if (isWebEnvironment(environment) && !this.webEnvironment) {
        environment = convertToStandardEnvironment(environment);
      }

      if (this.bannerMode != Banner.Mode.OFF) { // 是否在控制臺上打印自定義的banner
        printBanner(environment);
      }

      // Create, load, refresh and run the ApplicationContext
      context = createApplicationContext(); // 創(chuàng)建Spring容器
      context.setEnvironment(environment); // 設(shè)置Spring容器的環(huán)境信息
      postProcessApplicationContext(context); // 回調(diào)方法,Spring容器創(chuàng)建之后做一些額外的事
      applyInitializers(context); // SpringApplication的的初始化器開始工作
      // 遍歷調(diào)用SpringApplicationRunListener的contextPrepared方法。目前只是將這個事件廣播器注冊到Spring容器中
      listeners.contextPrepared(context);
      if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
      }

      // 把應(yīng)用程序參數(shù)持有類注冊到Spring容器中,并且是一個單例
      context.getBeanFactory().registerSingleton("springApplicationArguments",
          applicationArguments);

      Set<Object> sources = getSources();
      Assert.notEmpty(sources, "Sources must not be empty");
      load(context, sources.toArray(new Object[sources.size()]));
      // 廣播出ApplicationPreparedEvent事件給相應(yīng)的監(jiān)聽器執(zhí)行
      listeners.contextLoaded(context);

      // Spring容器的刷新
      refresh(context);
      if (this.registerShutdownHook) {
        try {
          context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
          // Not allowed in some environments.
        }
      }
      return context;
    }

Spring容器的創(chuàng)建createApplicationContext方法如下:

    protected ConfigurableApplicationContext createApplicationContext() {
      Class<?> contextClass = this.applicationContextClass;
      if (contextClass == null) {
        try {
          // 如果是web程序,那么構(gòu)造org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext容器
          // 否則構(gòu)造org.springframework.context.annotation.AnnotationConfigApplicationContext容器
          contextClass = Class.forName(this.webEnvironment
              ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
          throw new IllegalStateException(
              "Unable create a default ApplicationContext, "
                  + "please specify an ApplicationContextClass",
              ex);
        }
      }
      return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }

Spring容器創(chuàng)建之后有個回調(diào)方法postProcessApplicationContext:

    protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        if (this.webEnvironment) { // 如果是web程序
            if (context instanceof ConfigurableWebApplicationContext) { // 并且也是Spring Web容器
                ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
                if (this.beanNameGenerator != null) { // 如果SpringApplication設(shè)置了是實例命名生成器,注冊到Spring容器中
                    configurableContext.getBeanFactory().registerSingleton(
                            AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                            this.beanNameGenerator);
                }
            }
        }
        if (this.resourceLoader != null) { // 如果SpringApplication設(shè)置了資源加載器,設(shè)置到Spring容器中
            if (context instanceof GenericApplicationContext) {
                ((GenericApplicationContext) context)
                        .setResourceLoader(this.resourceLoader);
            }
            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader) context)
                        .setClassLoader(this.resourceLoader.getClassLoader());
            }
        }
    }

初始化器做的工作,比如ContextIdApplicationContextInitializer會設(shè)置應(yīng)用程序的id;AutoConfigurationReportLoggingInitializer會給應(yīng)用程序添加一個條件注解解析器報告等:

    protected void applyInitializers(ConfigurableApplicationContext context) {
      // 遍歷每個初始化器,對調(diào)用對應(yīng)的initialize方法
      for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
            initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
      }
    }

Spring容器的刷新refresh方法內(nèi)部會做很多很多的事情:比如BeanFactory的設(shè)置,BeanFactoryPostProcessor接口的執(zhí)行、BeanPostProcessor接口的執(zhí)行、自動化配置類的解析、條件注解的解析、國際化的初始化等等。這部分內(nèi)容會在之后的文章中進(jìn)行講解。

run方法中的Spring容器創(chuàng)建完成之后會調(diào)用afterRefresh方法,代碼如下:

    protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
      afterRefresh(context, args.getSourceArgs()); // 目前是個空實現(xiàn)
      callRunners(context, args); // 調(diào)用Spring容器中的ApplicationRunner和CommandLineRunner接口的實現(xiàn)類
    }

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<Object>();
      // 找出Spring容器中ApplicationRunner接口的實現(xiàn)類
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
      // 找出Spring容器中CommandLineRunner接口的實現(xiàn)類
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
      // 對runners進(jìn)行排序
        AnnotationAwareOrderComparator.sort(runners);
      // 遍歷runners依次執(zhí)行
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            if (runner instanceof ApplicationRunner) { // 如果是ApplicationRunner,進(jìn)行ApplicationRunner的run方法調(diào)用
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) { // 如果是CommandLineRunner,進(jìn)行CommandLineRunner的run方法調(diào)用
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

這樣run方法執(zhí)行完成之后。Spring容器也已經(jīng)初始化完成,各種監(jiān)聽器和初始化器也做了相應(yīng)的工作。

總結(jié)

SpringBoot啟動的時候,不論調(diào)用什么方法,都會構(gòu)造一個SpringApplication的實例,然后調(diào)用這個實例的run方法,這樣就表示啟動SpringBoot。

在run方法調(diào)用之前,也就是構(gòu)造SpringApplication的時候會進(jìn)行初始化的工作,初始化的時候會做以下幾件事:

  1. 把參數(shù)sources設(shè)置到SpringApplication屬性中,這個sources可以是任何類型的參數(shù)。本文的例子中這個sources就是MyApplication的class對象
  2. 判斷是否是web程序,并設(shè)置到webEnvironment這個boolean屬性中
  3. 找出所有的初始化器,默認(rèn)有5個,設(shè)置到initializers屬性中
  4. 找出所有的應(yīng)用程序監(jiān)聽器,默認(rèn)有9個,設(shè)置到listeners屬性中
  5. 找出運行的主類(main class)

SpringApplication構(gòu)造完成之后調(diào)用run方法,啟動SpringApplication,run方法執(zhí)行的時候會做以下幾件事:

  1. 構(gòu)造一個StopWatch,觀察SpringApplication的執(zhí)行
  2. 找出所有的SpringApplicationRunListener并封裝到SpringApplicationRunListeners中,用于監(jiān)聽run方法的執(zhí)行。監(jiān)聽的過程中會封裝成事件并廣播出去讓初始化過程中產(chǎn)生的應(yīng)用程序監(jiān)聽器進(jìn)行監(jiān)聽
  3. 構(gòu)造Spring容器(ApplicationContext),并返回
    3.1 創(chuàng)建Spring容器的判斷是否是web環(huán)境,是的話構(gòu)造AnnotationConfigEmbeddedWebApplicationContext,否則構(gòu)造AnnotationConfigApplicationContext
    3.2 初始化過程中產(chǎn)生的初始化器在這個時候開始工作
    3.3 Spring容器的刷新(完成bean的解析、各種processor接口的執(zhí)行、條件注解的解析等等)
  4. 從Spring容器中找出ApplicationRunner和CommandLineRunner接口的實現(xiàn)類并排序后依次執(zhí)行

例子

寫了一個例子用來驗證分析的啟動邏輯,包括自定義的初始化器、監(jiān)聽器、ApplicationRunner和CommandLineRunner。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內(nèi)容