1. spring boot的啟動(dòng)流程分析

環(huán)境:

  • spring boot 2.0.8.release
  • win10
  • idea 2018.03.05
    本文查看相關(guān)的文章與源代碼總結(jié)的码泛。

1. spring boot 啟動(dòng)流程

查看 SpringBoot啟動(dòng)流程解析 這篇文章窖铡,那里的啟動(dòng)結(jié)構(gòu)圖還是不錯(cuò)的。
啟動(dòng)流程主要分為三部分盈电,

  • 第一部分, 進(jìn)行SpringApplication的初始化模塊蓄拣,配置一些環(huán)境變量辽故、資源、構(gòu)造器风宁、監(jiān)聽器洁墙、主方法的類;
  • 第二部分戒财,實(shí)現(xiàn)應(yīng)用的啟動(dòng)热监,包括啟動(dòng)流程的監(jiān)聽模塊、加載配置環(huán)境模塊饮寞、核心的創(chuàng)建上下文環(huán)境模塊孝扛;
  • 第三部分,自動(dòng)化配置模塊幽崩,該模塊作為spring boot的自動(dòng)配置核心苦始。

查看一下的圖,這個(gè)圖比較精彩慌申。

1

2

3

4

如果不夠全面陌选,就查看[SpringBoot啟動(dòng)結(jié)構(gòu)圖] (https://www.processon.com/view/link/59812124e4b0de2518b32b6e)

2. 啟動(dòng)

每個(gè)SpringBoot程序都有一個(gè)主入口,也就是main方法太示,main里面調(diào)用SpringApplication.run()啟動(dòng)整個(gè)spring-boot程序柠贤,該方法所在類需要使用@SpringBootApplication注解,以及@ImportResource注解(if need)类缤,@SpringBootApplication包括三個(gè)注解臼勉,功能如下:

  • @EnableAutoConfiguration:SpringBoot根據(jù)應(yīng)用所聲明的依賴來對(duì)Spring框架進(jìn)行自動(dòng)配置

  • @SpringBootConfiguration(內(nèi)部為@Configuration):被標(biāo)注的類等于在spring的XML配置文件中(applicationContext.xml),裝配所有bean事務(wù)餐弱,提供了一個(gè)spring的上下文環(huán)境

  • @ComponentScan:組件掃描宴霸,可自動(dòng)發(fā)現(xiàn)和裝配Bean囱晴,默認(rèn)掃描SpringApplication的run方法里的Demo.class所在的包路徑下文件,所以最好將該啟動(dòng)類放到根包路徑下

1

3. 初始化模塊

SpringBoot啟動(dòng)類進(jìn)入run()方法瓢谢,首先會(huì)創(chuàng)建SpringApplication實(shí)例畸写,然后在構(gòu)造函數(shù)內(nèi),初始化initialize.

public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }
------------------------------------------
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
------------------------------------------
public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }
-------------------------------------------

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
                // 配置類加載器
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
                // 配置sources
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                // 判斷應(yīng)用的類型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
                // 創(chuàng)建初始化構(gòu)造器
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));氓扛、
                // 配置監(jiān)聽器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
                // 配置應(yīng)用主類
        this.mainApplicationClass = deduceMainApplicationClass();
    }

其中WebApplicationType.deduceFromClasspath();主要配置是否為Web環(huán)境枯芬, REACTIVE環(huán)境,None(單jar)采郎。

static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

注意: classUtils.isPresent(className, null)判斷所提供的類名的類是否存在千所,且可以被加載
java.lang.Class.forName()的作用是什么?
返回與給定字符串名的類或接口的Class對(duì)象蒜埋,使用給定的類加載器淫痰。

private static final String WEBFLUX_INDICATOR_CLASS = "org."
            + "springframework.web.reactive.DispatcherHandler";
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
            + "web.servlet.DispatcherServlet";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

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

4. 應(yīng)用的啟動(dòng)

應(yīng)用的啟動(dòng),會(huì)啟動(dòng)監(jiān)聽器整份、配置環(huán)境模塊待错、配置Banner、配置應(yīng)用上下文模塊

public ConfigurableApplicationContext run(String... args) {
                // 開啟計(jì)時(shí)器開始計(jì)時(shí)
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
                // headless模式配置
        configureHeadlessProperty();
                // 配置監(jiān)聽器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
                        // 配置環(huán)境變量
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
                        // 配置Banner
            Banner printedBanner = printBanner(environment);
                        // 創(chuàng)建應(yīng)用上下文
            context = createApplicationContext();
                        // 獲取springboot 異常類
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
                        // 更新應(yīng)用上下文烈评,prepareContext方法將listeners火俄、environment、applicationArguments础倍、banner等重要組件與上下文對(duì)象關(guān)聯(lián)
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
                        //  (初始化方法如下)將是實(shí)現(xiàn)spring-boot-starter-*(mybatis烛占、redis等)自動(dòng)化配置的關(guān)鍵,包括spring.factories的加載沟启,bean的實(shí)例化等核心工作忆家。
            refreshContext(context);
// Springboot做了一些基本的收尾工作,返回了應(yīng)用環(huán)境上下文
            afterRefresh(context, applicationArguments);
// 停止計(jì)時(shí)
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
//  這里會(huì)檢測是否Runner德迹,在項(xiàng)目啟動(dòng)后運(yùn)行的芽卿,一般是CommandRunner、ApplicationRunner
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

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

該方法中實(shí)現(xiàn)了如下幾個(gè)關(guān)鍵步驟:

    1. 創(chuàng)建了應(yīng)用的監(jiān)聽器SpringApplicationRunListeners并開始監(jiān)聽
    1. 加載SpringBoot配置環(huán)境(ConfigurableEnvironment)胳搞,如果是通過web容器發(fā)布卸例,會(huì)加載StandardEnvironment,其最終也是繼承了ConfigurableEnvironment肌毅,類圖如下
image.png

可以看出筷转,*Environment最終都實(shí)現(xiàn)了PropertyResolver接口,我們平時(shí)通過environment對(duì)象獲取配置文件中指定Key對(duì)應(yīng)的value方法時(shí)悬而,就是調(diào)用了propertyResolver接口的getProperty方法

    1. 配置環(huán)境(Environment)加入到監(jiān)聽器對(duì)象中(SpringApplicationRunListeners)
    1. 創(chuàng)建run方法的返回對(duì)象:ConfigurableApplicationContext(應(yīng)用配置上下文)呜舒,我們可以看一下創(chuàng)建方法:
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);
    }

方法會(huì)先獲取顯式設(shè)置的應(yīng)用上下文(applicationContextClass),如果不存在笨奠,再加載默認(rèn)的環(huán)境配置(通過是否是web environment判斷)袭蝗,默認(rèn)選擇AnnotationConfigApplicationContext注解上下文(通過掃描所有注解類來加載bean)唤殴,最后通過BeanUtils實(shí)例化上下文對(duì)象,并返回到腥,ConfigurableApplicationContext類圖如下:


image.png

主要看其繼承的兩個(gè)方向:

LifeCycle:生命周期類朵逝,定義了start啟動(dòng)、stop結(jié)束乡范、isRunning是否運(yùn)行中等生命周期空值方法

ApplicationContext:應(yīng)用上下文類配名,其主要繼承了beanFactory(bean的工廠類)

  • 5.回到run方法內(nèi),prepareContext方法將listeners篓足、environment段誊、applicationArguments闰蚕、banner等重要組件與上下文對(duì)象關(guān)聯(lián)

  • 6.接下來的refreshContext(context)方法(初始化方法如下)將是實(shí)現(xiàn)spring-boot-starter-*(mybatis栈拖、redis等)自動(dòng)化配置的關(guān)鍵,包括spring.factories的加載没陡,bean的實(shí)例化等核心工作涩哟。


private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
              // 注冊(cè)關(guān)閉的鉤子函數(shù),用于收尾工作
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }
-----------------------
protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }
-----------------------
public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
// 刷新盼玄,檢測環(huán)境變量
            this.prepareRefresh();
// 獲取ConfigurableListableBeanFactory工廠
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                } 
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            } 
        }
    }
  • refresh方法

配置結(jié)束后贴彼,Springboot做了一些基本的收尾工作,返回了應(yīng)用環(huán)境上下文埃儿。
回顧整體流程器仗,Springboot的啟動(dòng),主要?jiǎng)?chuàng)建了配置環(huán)境(environment)童番、事件監(jiān)聽(listeners)精钮、應(yīng)用上下文(applicationContext),并基于以上條件剃斧,在容器中開始實(shí)例化我們需要的Bean轨香,至此,通過SpringBoot啟動(dòng)的程序已經(jīng)構(gòu)造完成幼东,接下來我們來探討自動(dòng)化配置是如何實(shí)現(xiàn)臂容。

引用:

  1. https://www.cnblogs.com/trgl/p/7353782.html
  2. https://blog.csdn.net/doegoo/article/details/52471310

PS: 若你覺得可以、還行根蟹、過得去脓杉、甚至不太差的話,可以“關(guān)注”一下简逮,就此謝過!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末球散,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子买决,更是在濱河造成了極大的恐慌沛婴,老刑警劉巖吼畏,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘁灯,居然都是意外死亡泻蚊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門丑婿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來性雄,“玉大人,你說我怎么就攤上這事羹奉∶胄” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵诀拭,是天一觀的道長迁筛。 經(jīng)常有香客問我,道長耕挨,這世上最難降的妖魔是什么细卧? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮筒占,結(jié)果婚禮上贪庙,老公的妹妹穿的比我還像新娘。我一直安慰自己翰苫,他們只是感情好止邮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奏窑,像睡著了一般导披。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上良哲,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天盛卡,我揣著相機(jī)與錄音,去河邊找鬼筑凫。 笑死滑沧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巍实。 我是一名探鬼主播滓技,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼棚潦!你這毒婦竟也來了令漂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叠必,沒想到半個(gè)月后荚孵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纬朝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年收叶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片共苛。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡判没,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隅茎,到底是詐尸還是另有隱情澄峰,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布辟犀,位于F島的核電站俏竞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏踪蹬。R本人自食惡果不足惜胞此,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望跃捣。 院中可真熱鬧,春花似錦夺蛇、人聲如沸疚漆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娶聘。三九已至,卻和暖如春甚脉,著一層夾襖步出監(jiān)牢的瞬間丸升,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工牺氨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狡耻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓猴凹,卻偏偏與公主長得像夷狰,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子郊霎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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