深入學(xué)習(xí) Spring Boot:Spring Boot啟動(dòng)分析(下)

文接 深入學(xué)習(xí) Spring Boot:Spring Boot啟動(dòng)分析(上)


2础嫡、啟動(dòng) Spring 應(yīng)用程序run()

Spring Boot首先幫我們實(shí)例化了一個(gè)SpringApplication對(duì)象屡立,接下類(lèi)調(diào)用這個(gè)對(duì)象的run方法,創(chuàng)建和刷新一個(gè)新的ApplicationContext

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, exceptionReporters, ex);
        throw new IllegalStateException(ex);
    }
}
  • 2.1 啟動(dòng)一個(gè)StopWatch測(cè)量程序的運(yùn)行時(shí)間柳击。StopWatch可以在開(kāi)發(fā)和調(diào)試階段驗(yàn)證程序的性能

  • 2.2 使用Headless模式:

System.setProperty("java.awt.headless",System.getProperty("java.awt.headless", true);

Headless模式是系統(tǒng)的一種配置模式。在該模式下坤候,系統(tǒng)缺少了顯示設(shè)備喜每、鍵盤(pán)或鼠標(biāo)。Spring Boot程序一般是服務(wù)端程序是己,服務(wù)器往往缺少前述硬件又兵,但又需要使用他們提供的功能,生成相應(yīng)的數(shù)據(jù)卒废,以提供給客戶端(比如在console生成spring神獸沛厨,繪制驗(yàn)證碼之類(lèi)的)。此時(shí)升熊,我們可以在程序開(kāi)始激活headless模式俄烁,告訴程序,現(xiàn)在你要工作在Headless mode下级野,就不要指望硬件幫忙了页屠,你得自力更生,依靠系統(tǒng)的計(jì)算能力模擬出這些特性來(lái)蓖柔。

  • 2.3 通過(guò)SpringFactoriesLoader加載SpringApplicationRunListener
    并啟動(dòng)辰企,用以監(jiān)聽(tīng)SpringApplication的run方法產(chǎn)生的各類(lèi)事件。SpringApplicationRunListeners是對(duì)SpringApplicationRunListener實(shí)例集合的一個(gè)封裝况鸣。在這里牢贸,SpringApplication只加載了一個(gè)Listener:EventPublishingRunListener,事實(shí)上這個(gè)類(lèi)是充當(dāng)著事件廣播器的作用镐捧,它可以將run方法中的事件(如starting潜索、environmentPrepared)封裝為Event對(duì)象,發(fā)布到我們之前加載的ApplicationListener中懂酱。

  • 2.4 創(chuàng)建和配置ConfigurableEnvironment:

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

a竹习、根據(jù)我們之前檢測(cè)出來(lái)的webApplicationType創(chuàng)建一個(gè)ConfigurableEnvironment對(duì)象(若this.webApplicationType == WebApplicationType.SERVLET則創(chuàng)建其子類(lèi)StandardServletEnvironment()對(duì)象,否則創(chuàng)建StandardEnvironment對(duì)象)列牺。在實(shí)例化一個(gè)ConfigurableEnvironment對(duì)象時(shí)整陌,程序會(huì)讀取運(yùn)行環(huán)境的各種數(shù)據(jù),如"java.vm.version" -> "25.73-b02"瞎领;
b泌辫、將我們?cè)趩?dòng)應(yīng)用程序時(shí)帶入的參數(shù)(如“--debug”),配置到ConfigurableEnvironment對(duì)象中九默;
c震放、調(diào)用listeners.environmentPrepared(environment),發(fā)布事件荤西。在這里澜搅,EventPublishingRunListener會(huì)封裝一個(gè)ApplicationEnvironmentPreparedEvent伍俘,然后發(fā)布到各個(gè)Listener中。Listener執(zhí)行的動(dòng)作我們暫時(shí)不分析勉躺,明顯可以看到的就是在debug模式下癌瘾,console會(huì)打印第一行日志,顯示應(yīng)用程序的運(yùn)行環(huán)境饵溅;
d妨退、將environment綁定到SpringApplication上
e、在environment.propertySources中添加(如果沒(méi)有的話)一個(gè)ConfigurationPropertySourcesPropertySource對(duì)象蜕企,使得environment管理的PropertySource對(duì)象能適配 PropertySourcesPropertyResolver咬荷,能夠通過(guò)屬性名get到具體的配置,詳細(xì)見(jiàn) ConfigurationPropertySources

  • 2.5 將系統(tǒng)屬性“spring.beaninfo.ignore”設(shè)置為true轻掩,跳過(guò)掃描BeanInfo類(lèi)幸乒,防止重復(fù)加載bean。詳見(jiàn)IGNORE_BEANINFO_PROPERTY_NAME

  • 2.6 調(diào)用printBanner打印“Spring神獸”

  • 2.7 根據(jù)webApplicationType創(chuàng)建一個(gè)ConfigurableApplicationContext對(duì)象唇牧。在本例中罕扎,webApplicationType為”SERVLET“,創(chuàng)建的對(duì)象為"org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"

  • 2.8 加載spring.factories中的SpringBootExceptionReporter實(shí)現(xiàn)類(lèi)

  • 2.9 準(zhǔn)備Context:

private void prepareContext(ConfigurableApplicationContext context,
                            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // Add boot specific singleton beans
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[sources.size()]));
    listeners.contextLoaded(context);
}

a丐重、setEnvironment(environment)
b腔召、postProcessApplicationContext(context),做一些善后工作:如果成員變量beanNameGenerator不為Null扮惦,那么為ApplicationContext對(duì)象注冊(cè)beanNameGenerator bean臀蛛;如果成員變量resourceLoader不為null,則為ApplicationContext對(duì)象設(shè)置ResourceLoader崖蜜。

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    if (this.beanNameGenerator != null) {
        context.getBeanFactory().registerSingleton(
                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context)
                    .setResourceLoader(this.resourceLoader);
        }
        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context)
                    .setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
}

c浊仆、依次調(diào)用SpringApplication的initializers中的初始化器:

protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

在本例中,initializers列表及其任務(wù)為:

initializers = {ArrayList@952}  size = 6
// 讀取key為”context.initializer.classes“的配置豫领,實(shí)例化Initializer并執(zhí)行initialize();
 0 = {DelegatingApplicationContextInitializer@1057} 
// 設(shè)置context的id氧卧,本例等于”application“;
 1 = {ContextIdApplicationContextInitializer@1058} 
// 為context.beanFactoryPostProcessor增加一個(gè)ConfigurationWarningsPostProcessor對(duì)象氏堤,報(bào)告warning等級(jí)的錯(cuò)誤配置
2 = {ConfigurationWarningsApplicationContextInitializer@1059} 
// 為context添加一個(gè)ApplicationListener,監(jiān)聽(tīng)WebServerInitializedEvent
 3 = {ServerPortInfoApplicationContextInitializer@1060} 
// 為context.beanFactoryPostProcessor增加一個(gè)CachingMetadataReaderFactoryPostProcessor
 4 = {SharedMetadataReaderFactoryContextInitializer@1061} 
//為context添加一個(gè)AutoConfigurationReportListener搏明,用以監(jiān)聽(tīng)自動(dòng)配置報(bào)告
 5 = {AutoConfigurationReportLoggingInitializer@1062} 

d鼠锈、調(diào)用監(jiān)聽(tīng)器,報(bào)告contextPrepared事件星著;
e购笆、打印啟動(dòng)日志:

2017-09-10 20:18:51.937  INFO 28314 --- [           main] com.zhuangqf.learn.App                   : Starting App on zhuangqinfa with PID 28314 (/home/zhuangqf/workspace/spring/SpringBootDemo/target/classes started by zhuangqf in /home/zhuangqf/workspace/spring/SpringBootDemo)
2017-09-10 20:18:51.939  INFO 28314 --- [           main] com.zhuangqf.learn.App                   : No active profile set, falling back to default profiles: default

f、注冊(cè)指定的bean:"springApplicationArguments"和"springBootBanner":

// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
    context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}

簡(jiǎn)單而言虚循, context的beanFactory是一個(gè)DefaultListableBeanFactory對(duì)象同欠,其內(nèi)部有多個(gè)Map<String, Object> 用來(lái)存放注冊(cè)的bean样傍。
g、加載context.primarySources铺遂,在本例中為App.class衫哥,其主要是加載App上的注解或默認(rèn)的配置文件。

  • 2.10 refreshContext(context)
    加載或刷新持久化形式的配置(如xml文件襟锐、properties文件撤逢,和數(shù)據(jù)庫(kù)信息)。
    由于這是一個(gè)啟動(dòng)方法, 如果它失敗了, 它應(yīng)該銷(xiāo)毀已經(jīng)創(chuàng)建的單例, 以避免懸空的資源粮坞。換言之, 在調(diào)用該方法之后, 所有的單例bean都不應(yīng)實(shí)例化蚊荣。
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

整個(gè)方法比較復(fù)雜,我們一點(diǎn)點(diǎn)解析:
a莫杈、準(zhǔn)備刷新prepareRefresh()互例,完成了以下任務(wù):
設(shè)置context的startupDate為當(dāng)前時(shí)間;
設(shè)置closed標(biāo)志位為false筝闹,active標(biāo)志為true媳叨;
讀取Property配置到environment中;
檢查environment必設(shè)的配置是否為null丁存;
初始化this.earlyApplicationEvents作為存放發(fā)布時(shí)間的Set
b肩杈、obtainFreshBeanFactory() :銷(xiāo)毀原有的beanFactory類(lèi),并新建一個(gè)beanFactory返回
c解寝、prepareBeanFactory(beanFactory):設(shè)置beanFactory的各個(gè)屬性扩然,由AbstractApplicationContext實(shí)現(xiàn);
d聋伦、postProcessBeanFactory(beanFactory):對(duì)beanFactory根據(jù)具體的Context子類(lèi)設(shè)置不同的屬性夫偶,例如,本例中context的具體類(lèi)型為ServletWebServerApplicationContext觉增,會(huì)在beanFactory中注冊(cè)一個(gè)ServletContextAwareProcessor兵拢。
e、invokeBeanFactoryPostProcessors(beanFactory);調(diào)用注冊(cè)到beanFactory中的postRrocessors逾礁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末说铃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嘹履,更是在濱河造成了極大的恐慌腻扇,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砾嫉,死亡現(xiàn)場(chǎng)離奇詭異幼苛,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)焕刮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)舶沿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)墙杯,“玉大人,你說(shuō)我怎么就攤上這事括荡「吒洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵一汽,是天一觀的道長(zhǎng)避消。 經(jīng)常有香客問(wèn)我,道長(zhǎng)召夹,這世上最難降的妖魔是什么岩喷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮监憎,結(jié)果婚禮上纱意,老公的妹妹穿的比我還像新娘。我一直安慰自己鲸阔,他們只是感情好偷霉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著褐筛,像睡著了一般类少。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渔扎,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天硫狞,我揣著相機(jī)與錄音,去河邊找鬼晃痴。 笑死残吩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的倘核。 我是一名探鬼主播泣侮,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼紧唱!你這毒婦竟也來(lái)了活尊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤漏益,失蹤者是張志新(化名)和其女友劉穎酬凳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體遭庶,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年稠屠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了峦睡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翎苫。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖榨了,靈堂內(nèi)的尸體忽然破棺而出煎谍,到底是詐尸還是另有隱情,我是刑警寧澤龙屉,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布呐粘,位于F島的核電站,受9級(jí)特大地震影響转捕,放射性物質(zhì)發(fā)生泄漏作岖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一五芝、第九天 我趴在偏房一處隱蔽的房頂上張望痘儡。 院中可真熱鬧,春花似錦枢步、人聲如沸沉删。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)矾瑰。三九已至,卻和暖如春隘擎,著一層夾襖步出監(jiān)牢的瞬間殴穴,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工嵌屎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留推正,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓宝惰,卻偏偏與公主長(zhǎng)得像植榕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尼夺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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