SpringBoot啟動流程分析2:SpringApplication的run方法

目錄

一、前言
二沛豌、SpringBoot啟動流程梳理
三庸娱、第一步:獲取并啟動監(jiān)聽器
四链峭、第二步:構(gòu)造應(yīng)用上下文環(huán)境

4.1畦娄、 ConfigurableEnvironment environment = getOrCreateEnvironment();
4.2、 configureEnvironment(environment, applicationArguments.getSourceArgs());
4.3熏版、 listeners.environmentPrepared(environment);

五纷责、第三步:初始化應(yīng)用上下文

一捍掺、前言

前一篇介紹了 SpringApplication 類的實例化過程撼短,本章總結(jié)SpringBoot啟動流程最重要的部分run方法。

通過run方法梳理出SpringBoot啟動的流程挺勿,然后后面的博客再一步步的分析啟動流程中各個步驟所做的具體的工作曲横。深入分析后會發(fā)現(xiàn)SpringBoot也就是給Spring包了一層皮,事先替我們準(zhǔn)備好Spring所需要的環(huán)境及一些基礎(chǔ),具體通過源碼一步步深入分析后會發(fā)現(xiàn)Spring是真的很偉大禾嫉。當(dāng)然跟代碼的時候越深入越容易陷進(jìn)去進(jìn)而發(fā)現(xiàn)有些東西沒法通過博客詳細(xì)的梳理出來灾杰。當(dāng)然在這個過程中還是立足于我們對SpringBoot的使用來說明源碼所做的工作。知其然才能知其所以然熙参。加油

二艳吠、SpringBoot啟動流程梳理

首先擺上run方法的源碼

    public ConfigurableApplicationContext run(String... args) {
        //記錄程序運行時間
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // ConfigurableApplicationContext Spring 的上下文
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        //從META-INF/spring.factories中獲取監(jiān)聽器
        //1、獲取并啟動監(jiān)聽器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //2孽椰、構(gòu)造應(yīng)用上下文環(huán)境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            //處理需要忽略的Bean
            this.configureIgnoreBeanInfo(environment);
            //打印banner
            Banner printedBanner = this.printBanner(environment);
            //3昭娩、初始化應(yīng)用上下文
            context = this.createApplicationContext();
            //實例化SpringBootExceptionReporter.class,用來支持報告關(guān)于啟動的錯誤
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            //4黍匾、刷新應(yīng)用上下文前的準(zhǔn)備階段
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //5栏渺、刷新應(yīng)用上下文
            this.refreshContext(context);
            //刷新應(yīng)用上下文后的擴(kuò)展接口
            this.afterRefresh(context, applicationArguments);
            //時間記錄停止
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            //發(fā)布容器啟動完成事件
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

具體的每一行代碼的含義請看注釋,我們在這先總結(jié)一下啟動過程中的重要步驟:(筆者傾向于將應(yīng)用上下文同容器區(qū)分開來)

第一步:獲取并啟動監(jiān)聽器
第二步:構(gòu)造應(yīng)用上下文環(huán)境
第三步:初始化應(yīng)用上下文
第四步:刷新應(yīng)用上下文前的準(zhǔn)備階段
第五步:刷新應(yīng)用上下文
第六步:刷新應(yīng)用上下文后的擴(kuò)展接口

OK锐涯,下面SpringBoot的啟動流程分析磕诊,我們就根據(jù)這6大步驟進(jìn)行詳細(xì)解讀。最總要的是第四纹腌,五步霎终。我們會著重的分析。

三壶笼、第一步:獲取并啟動監(jiān)聽器

事件機制在Spring是很重要的一部分內(nèi)容神僵,通過事件機制我們可以監(jiān)聽Spring容器中正在發(fā)生的一些事件,同樣也可以自定義監(jiān)聽事件覆劈。Spring的事件為Bean和Bean之間的消息傳遞提供支持保礼。當(dāng)一個對象處理完某種任務(wù)后,通知另外的對象進(jìn)行某些處理责语,常用的場景有進(jìn)行某些操作后發(fā)送通知炮障,消息、郵件等情況坤候。

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

在這里面是不是看到一個熟悉的方法:getSpringFactoriesInstances()胁赢,可以看下下面的注釋,前面的博文我們已經(jīng)詳細(xì)介紹過該方法是怎么一步步的獲取到META-INF/spring.factories中的指定的key的value白筹,獲取到以后怎么實例化類的智末。

/**
 * 通過指定的classloader 從META-INF/spring.factories獲取指定的Spring的工廠實例
 * @param type
 * @param parameterTypes
 * @param args
 * @param <T>
 * @return
 */
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        // Use names and ensure unique to protect against duplicates
        //通過指定的classLoader從 META-INF/spring.factories 的資源文件中,
        //讀取 key 為 type.getName() 的 value
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //創(chuàng)建Spring工廠實例
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        //對Spring工廠實例排序(org.springframework.core.annotation.Order注解指定的順序)
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

回到run方法徒河,debug這個代碼 SpringApplicationRunListeners listeners = getRunListeners(args); 看一下獲取的是哪個監(jiān)聽器:

image

EventPublishingRunListener監(jiān)聽器是Spring容器的啟動監(jiān)聽器系馆。

listeners.starting(); 開啟了監(jiān)聽事件。

四顽照、第二步:構(gòu)造應(yīng)用上下文環(huán)境

應(yīng)用上下文環(huán)境包括什么呢由蘑?包括計算機的環(huán)境闽寡,Java環(huán)境,Spring的運行環(huán)境尼酿,Spring項目的配置(在SpringBoot中就是那個熟悉的application.properties/yml)等等爷狈。

首先看一下prepareEnvironment()方法。

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        // Create and configure the environment
        //創(chuàng)建并配置相應(yīng)的環(huán)境
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        
        //根據(jù)用戶配置裳擎,配置 environment系統(tǒng)環(huán)境
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach((Environment)environment);
        // 啟動相應(yīng)的監(jiān)聽器涎永,其中一個重要的監(jiān)聽器 ConfigFileApplicationListener 就是加載項目配置文件的監(jiān)聽器。listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

看上面的注釋鹿响,方法中主要完成的工作土辩,首先是創(chuàng)建并按照相應(yīng)的應(yīng)用類型配置相應(yīng)的環(huán)境,然后根據(jù)用戶的配置抢野,配置系統(tǒng)環(huán)境拷淘,然后啟動監(jiān)聽器,并加載系統(tǒng)配置文件指孤。

4.1启涯、 ConfigurableEnvironment environment = getOrCreateEnvironment();

看看getOrCreateEnvironment()干了些什么。

private ConfigurableEnvironment getOrCreateEnvironment() {
      if (this.environment != null) {
          return this.environment;
      }
      //如果應(yīng)用類型是 SERVLET 則實例化 StandardServletEnvironment
      if (this.webApplicationType == WebApplicationType.SERVLET) { 
         return new StandardServletEnvironment(); 
     }
      return new StandardEnvironment();
 }

通過代碼可以看到根據(jù)不同的應(yīng)用類型初始化不同的系統(tǒng)環(huán)境實例恃轩。前面咱們已經(jīng)說過應(yīng)用類型是怎么判斷的了结洼,這里就不在贅述了。

image

從上面的繼承關(guān)系可以看出叉跛,StandardServletEnvironment是StandardEnvironment的子類松忍。這兩個對象也沒什么好講的,當(dāng)是web項目的時候筷厘,環(huán)境上會多一些關(guān)于web環(huán)境的配置鸣峭。

4.2、 configureEnvironment(environment, applicationArguments.getSourceArgs());

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
     // 將main 函數(shù)的args封裝成 SimpleCommandLinePropertySource 加入環(huán)境中酥艳。
     configurePropertySources(environment, args); 
     // 激活相應(yīng)的配置文件
     configureProfiles(environment, args); 7 
}

在執(zhí)行完方法中的兩行代碼后摊溶,debug的截圖如下

image

如下圖所示,我在spring的啟動參數(shù)中指定了參數(shù):--spring.profiles.active=prod(關(guān)于這個參數(shù)的用法充石,點我莫换,其實就是啟動多個實例用的)

image

在configurePropertySources(environment, args);中將args封裝成了SimpleCommandLinePropertySource并加入到了environment中。

configureProfiles(environment, args);根據(jù)啟動參數(shù)激活了相應(yīng)的配置文件骤铃。

話不多說拉岁,debug一遍就明白了。

4.3惰爬、 listeners.environmentPrepared(environment);

進(jìn)入到方法一路跟下去就到了SimpleApplicationEventMulticaster類的multicastEvent()方法喊暖。

image
image

查看getApplicationListeners(event, type)執(zhí)行結(jié)果,發(fā)現(xiàn)一個重要的監(jiān)聽器ConfigFileApplicationListener补鼻。

先看看這個類的注釋

/**
 * {@link EnvironmentPostProcessor} that configures the context environment by loading
 * properties from well known file locations. By default properties will be loaded from
 * 'application.properties' and/or 'application.yml' files in the following locations:
 * <ul>
 * <li>classpath:</li>
 * <li>file:./</li>
 * <li>classpath:config/</li>
 * <li>file:./config/:</li>
 * </ul>
 * <p>
 * Alternative search locations and names can be specified using
 * {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}.
 * <p>
 * Additional files will also be loaded based on active profiles. For example if a 'web'
 * profile is active 'application-web.properties' and 'application-web.yml' will be
 * considered.
 * <p>
 * The 'spring.config.name' property can be used to specify an alternative name to load
 * and the 'spring.config.location' property can be used to specify alternative search
 * locations or specific files.
 * <p>
 * 從默認(rèn)的位置加載配置文件哄啄,并將其加入 上下文的 environment變量中
 */

這個監(jiān)聽器默認(rèn)的從注釋中<ul>標(biāo)簽所示的幾個位置加載配置文件,并將其加入 上下文的 environment變量中风范。當(dāng)然也可以通過配置指定咨跌。

debug跳過 listeners.environmentPrepared(environment); 這一行,查看environment屬性硼婿,果真如上面所說的锌半,配置文件的配置信息已經(jīng)添加上來了。

image

五寇漫、第三步:初始化應(yīng)用上下文

在SpringBoot工程中刊殉,應(yīng)用類型分為三種,如下代碼所示州胳。

public enum WebApplicationType {
    /**
     * 應(yīng)用程序不是web應(yīng)用记焊,也不應(yīng)該用web服務(wù)器去啟動
     */
    NONE,
    /**
     * 應(yīng)用程序應(yīng)作為基于servlet的web應(yīng)用程序運行,并應(yīng)啟動嵌入式servlet web(tomcat)服務(wù)器栓撞。
     */
    SERVLET,
    /**
     * 應(yīng)用程序應(yīng)作為 reactive web應(yīng)用程序運行遍膜,并應(yīng)啟動嵌入式 reactive web服務(wù)器。
     */
    REACTIVE
}

對應(yīng)三種應(yīng)用類型瓤湘,SpringBoot項目有三種對應(yīng)的應(yīng)用上下文瓢颅,我們以web工程為例,即其上下文為AnnotationConfigServletWebServerApplicationContext弛说。

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
        + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
        + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_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);
}

我們先看一下AnnotationConfigServletWebServerApplicationContext的設(shè)計挽懦。

image

關(guān)于他的繼承體系,我們在前面的博客中<Spring IoC容器與應(yīng)用上下文的設(shè)計與實現(xiàn)>已經(jīng)詳細(xì)介紹了木人,在此不再贅述信柿。

應(yīng)用上下文可以理解成IoC容器的高級表現(xiàn)形式,應(yīng)用上下文確實是在IoC容器的基礎(chǔ)上豐富了一些高級功能醒第。

應(yīng)用上下文對IoC容器是持有的關(guān)系角塑。他的一個屬性beanFactory就是IoC容器(DefaultListableBeanFactory)。所以他們之間是持有淘讥,和擴(kuò)展的關(guān)系圃伶。

接下來看GenericApplicationContext類

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
    private final DefaultListableBeanFactory beanFactory;
    ...
    public GenericApplicationContext() {
        this.beanFactory = new DefaultListableBeanFactory();
    }
    ...
}

beanFactory正是在AnnotationConfigServletWebServerApplicationContext實現(xiàn)的接口GenericApplicationContext中定義的。在上面createApplicationContext()方法中的蒲列, BeanUtils.instantiateClass(contextClass) 這個方法中窒朋,不但初始化了AnnotationConfigServletWebServerApplicationContext類,也就是我們的上下文context蝗岖,同樣也觸發(fā)了GenericApplicationContext類的構(gòu)造函數(shù)侥猩,從而IoC容器也創(chuàng)建了。仔細(xì)看他的構(gòu)造函數(shù)抵赢,有沒有發(fā)現(xiàn)一個很熟悉的類DefaultListableBeanFactory欺劳,沒錯唧取,DefaultListableBeanFactory就是IoC容器真實面目了。在后面的refresh()方法分析中划提,DefaultListableBeanFactory是無處不在的存在感枫弟。

debug跳過createApplicationContext()方法。

image

如上圖所示鹏往,context就是我們熟悉的上下文(也有人稱之為容器淡诗,都可以,看個人愛好和理解)伊履,beanFactory就是我們所說的IoC容器的真實面孔了韩容。細(xì)細(xì)感受下上下文和容器的聯(lián)系和區(qū)別,對于我們理解源碼有很大的幫助唐瀑。在系列文章中群凶,我們也是將上下文和容器嚴(yán)格區(qū)分開來的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哄辣,一起剝皮案震驚了整個濱河市座掘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柔滔,老刑警劉巖溢陪,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異睛廊,居然都是意外死亡形真,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門超全,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咆霜,“玉大人,你說我怎么就攤上這事嘶朱《昱鳎” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵疏遏,是天一觀的道長脉课。 經(jīng)常有香客問我,道長财异,這世上最難降的妖魔是什么倘零? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮戳寸,結(jié)果婚禮上呈驶,老公的妹妹穿的比我還像新娘。我一直安慰自己疫鹊,他們只是感情好袖瞻,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布司致。 她就那樣靜靜地躺著,像睡著了一般聋迎。 火紅的嫁衣襯著肌膚如雪脂矫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天砌庄,我揣著相機與錄音,去河邊找鬼奕枢。 笑死娄昆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缝彬。 我是一名探鬼主播萌焰,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谷浅!你這毒婦竟也來了扒俯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤一疯,失蹤者是張志新(化名)和其女友劉穎撼玄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墩邀,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡掌猛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了眉睹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荔茬。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖竹海,靈堂內(nèi)的尸體忽然破棺而出慕蔚,到底是詐尸還是另有隱情,我是刑警寧澤斋配,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布孔飒,位于F島的核電站,受9級特大地震影響艰争,放射性物質(zhì)發(fā)生泄漏十偶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一园细、第九天 我趴在偏房一處隱蔽的房頂上張望惦积。 院中可真熱鬧,春花似錦猛频、人聲如沸狮崩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睦柴。三九已至诽凌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坦敌,已是汗流浹背侣诵。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狱窘,地道東北人杜顺。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像蘸炸,于是被迫代替她去往敵國和親躬络。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354