SpringBoot啟動流程分析

參考:

http://www.reibang.com/p/d51e1896a5f7
http://412887952-qq-com.iteye.com/blog/2345379

1.程序入口代碼:

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

2.進入之后會進入到SpringApplication.java類中:

    public static ConfigurableApplicationContext run(Object source, String... args) {
       return run(new Object[] { source }, args);
    }

3.進入重構run()方法:

    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
       return new SpringApplication(sources).run(args);
    }

4.這里new了一個SpringApplication對象出來蛹尝,然后調用其run方法氯夷,我們先看看這個new SpringApplication的過程:

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

5.看下具體的initialize()方法:

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

ApplicationContextInitializer枷遂,應用程序初始化器樱衷,做一些初始化的工作:

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

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

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

注意:此處的ApplicationListener有別于下面的SpringApplicationRunListener
這里的應用程序事件(ApplicationEvent)有應用程序啟動事件(ApplicationStartedEvent)酒唉,失敗事件(ApplicationFailedEvent)矩桂,準備事件(ApplicationPreparedEvent)等。

應用程序事件監(jiān)聽器跟監(jiān)聽事件是綁定的痪伦。比如ConfigServerBootstrapApplicationListener只跟ApplicationEnvironmentPreparedEvent事件綁定侄榴,LiquibaseServiceLocatorApplicationListener只跟ApplicationStartedEvent事件綁定,LoggingApplicationListener跟所有事件綁定等网沾。

默認情況下癞蚕,initialize方法從spring.factories文件中找出的key為ApplicationContextInitializer的類有:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer
org.springframework.boot.context.ContextIdApplicationContextInitializer
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

key為ApplicationListener的有:
org.springframework.boot.context.config.ConfigFileApplicationListener
org.springframework.boot.context.config.AnsiOutputApplicationListener
org.springframework.boot.logging.LoggingApplicationListener
org.springframework.boot.logging.ClasspathLoggingApplicationListener
org.springframework.boot.autoconfigure.BackgroundPreinitializer
org.springframework.boot.context.config.DelegatingApplicationListener
org.springframework.boot.builder.ParentContextCloserApplicationListener
org.springframework.boot.context.FileEncodingApplicationListener
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

從上面的代碼我們看到初始化做了以下幾件事情:

5.1 this.webEnvironment = deduceWebEnvironment();

這一個方法決定創(chuàng)建的是一個WEB應用還是一個SPRING的標準Standalone應用。如果入方法可以看到其是怎么判斷的:

private boolean deduceWebEnvironment() {
       for (String className : WEB_ENVIRONMENT_CLASSES) {
           if (!ClassUtils.isPresent(className, null)) {
              return false;
           }
       }
       return true;
    }

其中WEB_ENVIRONMENT_CLASSES是一個靜態(tài)常量數(shù)組:

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
         "org.springframework.web.context.ConfigurableWebApplicationContext" };

可以看到是根據(jù)org.springframework.util.ClassUtils的靜態(tài)方法去判斷classpath里面是否有WEB_ENVIRONMENT_CLASSES包含的類辉哥,如果有都包含則返回true則表示啟動一個WEB應用桦山,否則返回false啟動一個標準Spring的應用。

是否啟動一個WEB應用就是取決于classpath下是否有javax.servlet.Servlet和
org.springframework.web.context.ConfigurableWebApplicationContext醋旦。

5.2 進入下一個階段:

setInitializers((Collection) getSpringFactoriesInstances(
              ApplicationContextInitializer.class));
}

這個方法則是初始化classpath下的所有的可用的ApplicationContextInitializer

5.3 下一步:

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

這個方法則是初使化classpath下的所有的可用的ApplicationListener

5.4 下一步:

this.mainApplicationClass = deduceMainApplicationClass();

我們找到對應的deduceMainApplicationClass()方法:

   private Class<?> deduceMainApplicationClass() {
       try {
           StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
           for (StackTraceElement stackTraceElement : stackTrace) {
              if ("main".equals(stackTraceElement.getMethodName())) {
                  return Class.forName(stackTraceElement.getClassName());
              }
           }
       }
       catch (ClassNotFoundException ex) {
           // Swallow and continue
       }
       return null;
    }
}

最后找出main方法的全類名并返回其實例并設置到SpringApplication的this.mainApplicationClass完成初始化恒水。然后調用SpringApplication實例的run方法來啟動應用

SpringApplication的執(zhí)行

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

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

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

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

它定義了5個步驟:

  • started(run方法執(zhí)行的時候立馬執(zhí)行矢沿;對應事件的類型是ApplicationStartedEvent)
  • environmentPrepared(ApplicationContext創(chuàng)建之前并且環(huán)境信息準備好的時候調用;對應事件的類型是ApplicationEnvironmentPreparedEvent)
  • contextPrepared(ApplicationContext創(chuàng)建好并且在source加載之前調用一次酸纲;沒有具體的對應事件)
  • contextLoaded(ApplicationContext創(chuàng)建并加載之后并在refresh之前調用捣鲸;對應事件的類型是ApplicationPreparedEvent)
  • finished(run方法結束之前調用;對應事件的類型是ApplicationReadyEvent或ApplicationFailedEvent)

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

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

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


image

SpringApplication的run方法代碼如下:

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

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

private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
      ConfigurableApplicationContext context; // 定義Spring容器
      // 創(chuàng)建應用程序的環(huán)境信息次泽。如果是web程序,創(chuàng)建StandardServletEnvironment席爽;否則意荤,創(chuàng)建StandardEnvironment
      ConfigurableEnvironment environment = getOrCreateEnvironment();
      // 配置一些環(huán)境信息。比如profile只锻,命令行參數(shù)
      configureEnvironment(environment, applicationArguments.getSourceArgs());
      // 廣播出ApplicationEnvironmentPreparedEvent事件給相應的監(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); // 設置Spring容器的環(huán)境信息
      postProcessApplicationContext(context); // 回調方法玖像,Spring容器創(chuàng)建之后做一些額外的事
      applyInitializers(context); // SpringApplication的的初始化器開始工作
      // 遍歷調用SpringApplicationRunListener的contextPrepared方法。目前只是將這個事件廣播器注冊到Spring容器中
      listeners.contextPrepared(context);
      if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
      }

      // 把應用程序參數(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事件給相應的監(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程序捐寥,那么構造org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext容器
          // 否則構造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)建之后有個回調方法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設置了是實例命名生成器,注冊到Spring容器中
                    configurableContext.getBeanFactory().registerSingleton(
                            AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                            this.beanNameGenerator);
                }
            }
        }
        if (this.resourceLoader != null) { // 如果SpringApplication設置了資源加載器祖驱,設置到Spring容器中
            if (context instanceof GenericApplicationContext) {
                ((GenericApplicationContext) context)
                        .setResourceLoader(this.resourceLoader);
            }
            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader) context)
                        .setClassLoader(this.resourceLoader.getClassLoader());
            }
        }
    }

初始化器做的工作

比如ContextIdApplicationContextInitializer會設置應用程序的id握恳;AutoConfigurationReportLoggingInitializer會給應用程序添加一個條件注解解析器報告等:

protected void applyInitializers(ConfigurableApplicationContext context) {
      // 遍歷每個初始化器,對調用對應的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方法內部會做很多很多的事情:

比如BeanFactory的設置捺僻,BeanFactoryPostProcessor接口的執(zhí)行乡洼、BeanPostProcessor接口的執(zhí)行、自動化配置類的解析匕坯、條件注解的解析束昵、國際化的初始化等等。這部分內容會在之后的文章中進行講解醒颖。

run方法中的Spring容器創(chuàng)建完成之后會調用afterRefresh方法

代碼如下:

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
      afterRefresh(context, args.getSourceArgs()); // 目前是個空實現(xiàn)
      callRunners(context, args); // 調用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進行排序
        AnnotationAwareOrderComparator.sort(runners);
      // 遍歷runners依次執(zhí)行
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            if (runner instanceof ApplicationRunner) { // 如果是ApplicationRunner妻怎,進行ApplicationRunner的run方法調用
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) { // 如果是CommandLineRunner,進行CommandLineRunner的run方法調用
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

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

總結

SpringBoot啟動的時候,不論調用什么方法榛丢,都會構造一個SpringApplication的實例铲球,然后調用這個實例的run方法,這樣就表示啟動SpringBoot晰赞。

在run方法調用之前稼病,也就是構造SpringApplication的時候會進行初始化的工作,初始化的時候會做以下幾件事:

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

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

  1. 構造一個StopWatch妥凳,觀察SpringApplication的執(zhí)行
  2. 找出所有的SpringApplicationRunListener并封裝到SpringApplicationRunListeners中竟贯,用于監(jiān)聽run方法的執(zhí)行。監(jiān)聽的過程中會封裝成事件并廣播出去讓初始化過程中產(chǎn)生的應用程序監(jiān)聽器進行監(jiān)聽
  3. 構造Spring容器(ApplicationContext)逝钥,并返回
    3.1 創(chuàng)建Spring容器的判斷是否是web環(huán)境屑那,是的話構造AnnotationConfigEmbeddedWebApplicationContext,否則構造AnnotationConfigApplicationContext
    3.2 初始化過程中產(chǎn)生的初始化器在這個時候開始工作
    3.3 Spring容器的刷新(完成bean的解析艘款、各種processor接口的執(zhí)行齐莲、條件注解的解析等等)
  4. 從Spring容器中找出ApplicationRunner和CommandLineRunner接口的實現(xiàn)類并排序后依次執(zhí)行
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市磷箕,隨后出現(xiàn)的幾起案子选酗,更是在濱河造成了極大的恐慌,老刑警劉巖岳枷,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芒填,死亡現(xiàn)場離奇詭異,居然都是意外死亡空繁,警方通過查閱死者的電腦和手機殿衰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盛泡,“玉大人闷祥,你說我怎么就攤上這事“了校” “怎么了凯砍?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵箱硕,是天一觀的道長。 經(jīng)常有香客問我悟衩,道長剧罩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任座泳,我火速辦了婚禮惠昔,結果婚禮上,老公的妹妹穿的比我還像新娘挑势。我一直安慰自己镇防,他們只是感情好,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布潮饱。 她就那樣靜靜地躺著营罢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饼齿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天蝙搔,我揣著相機與錄音缕溉,去河邊找鬼。 笑死吃型,一個胖子當著我的面吹牛证鸥,可吹牛的內容都是我干的。 我是一名探鬼主播勤晚,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼枉层,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赐写?” 一聲冷哼從身側響起鸟蜡,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挺邀,沒想到半個月后揉忘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡端铛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年泣矛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禾蚕。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡您朽,死狀恐怖,靈堂內的尸體忽然破棺而出换淆,到底是詐尸還是另有隱情哗总,我是刑警寧澤几颜,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站魂奥,受9級特大地震影響菠剩,放射性物質發(fā)生泄漏。R本人自食惡果不足惜耻煤,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一具壮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧哈蝇,春花似錦棺妓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吠勘,卻和暖如春性芬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剧防。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工植锉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人峭拘。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓俊庇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸡挠。 傳聞我的和親對象是個殘疾皇子辉饱,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內容