SpringBoot成長(zhǎng)記2:從HelloWorld開始分析SpringBoot

file

上一節(jié)我們提到過,認(rèn)識(shí)一個(gè)新技術(shù)的時(shí)候攒岛,通常是從一個(gè)入門的HelloWorld開始赖临,之后閱讀它的一些入門文檔和書籍、視頻灾锯,從而掌握它的基本使用兢榨。

這一節(jié)我就來(lái)帶大家從HelloWorld開始,先摸清楚SpringBoot的核心脈絡(luò)顺饮,之后再來(lái)逐步分析透徹SpringBoot吵聪,從而精通它。

從搭建HelloWorld入口開始分析SpringBoot

首先我們從官方的文檔中搭建出一個(gè)2.2.2 版本的SpringBoot兼雄,增加了兩個(gè)starter吟逝,mybatis-plus-boot-starter、spring-boot-starter-web赦肋,使用Maven進(jìn)行項(xiàng)目和依賴管理块攒,配置一個(gè)本地的mysql。相信這個(gè)對(duì)你們來(lái)說(shuō)金砍,都比較簡(jiǎn)單局蚀,我就不一一進(jìn)行贅述了。

經(jīng)過上面的基本搭建恕稠,你就會(huì)有類似一個(gè)下面的一個(gè)SpringBoot HelloWorld級(jí)別 的入口琅绅。

package org.mfm.learn.springboot;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("org.mfm.learn.springboot.mapper")
@SpringBootApplication
public class LearnSpringBootApplication {

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

}

通過上一節(jié)你知道SpringBoot定義了一個(gè)SpringApplication的web應(yīng)用啟動(dòng)流程,入口通過一個(gè)java -jar的命令鹅巍,執(zhí)行main函數(shù)啟動(dòng)一個(gè)JVM進(jìn)程千扶,運(yùn)行內(nèi)部的tomcat監(jiān)聽一個(gè)默認(rèn)8080的端口,提供web服務(wù)骆捧。

file

整個(gè)過程中第一個(gè)最關(guān)鍵的就是SpringBoot定義的SpringApplication澎羞,我們一起先來(lái)看下它是怎么創(chuàng)建new的。

SpringApplication的創(chuàng)建時(shí)核心組件圖

SpringApplication的創(chuàng)建時(shí)的代碼分析

在上面的示例代碼中敛苇,main方法執(zhí)行了 SpringApplication的run方法,如下:

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);
}

run方法核心入?yún)⒕褪莔ain函數(shù)所在類+main函數(shù)args的參數(shù)妆绞,之后就直接創(chuàng)建了一個(gè)SpringApplication對(duì)象。讓我們一起來(lái)看看這個(gè)SpringBoot定義的概念怎么創(chuàng)建的枫攀,創(chuàng)建時(shí)的核心組件又有哪些呢括饶?

public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication的創(chuàng)建時(shí)核心脈絡(luò)

SpringApplication的創(chuàng)建核心脈絡(luò)比較簡(jiǎn)單:

1)ResourceLoader 指明資源加載器,這個(gè)暫時(shí)不清楚是啥東西来涨,默認(rèn)是個(gè)null图焰。

2)webApplicationType 推斷當(dāng)前web應(yīng)用類型,通過一個(gè)deduceFromClasspath方法推斷出的蹦掐。

3)之后設(shè)置了setInitializers技羔、setListeners兩個(gè)列表僵闯,分別是一堆Initializer和Listener,都是通過getSpringFactoriesInstances方法獲取的藤滥。

4)通過primarySources鳖粟、mainApplicationClass記錄了啟動(dòng)主要資源類,也就是之前HelloWorld中的LearnSpringBootApplication.class超陆。

上面是我第一次看這個(gè)類的一個(gè)脈絡(luò)后牺弹,腦中得到的結(jié)果。

file

你第一次看這里时呀,肯定什么都不清楚张漂,不知道每個(gè)變量有什么用,是干嘛的谨娜,沒關(guān)系的航攒,第一次,你只要熟悉它的脈絡(luò)就可以趴梢。知道這里設(shè)置了兩個(gè)集合變量Initializer和Listener漠畜,可以設(shè)置esourceLoader ,標(biāo)記了一些類型和類坞靶,這就夠了憔狞。

之后你有時(shí)間,再挨個(gè)去了解每個(gè)變量或者組件的作用就可以了彰阴,這個(gè)不還是先脈絡(luò)后細(xì)節(jié)的思想瘾敢,是吧?

SpringApplication的創(chuàng)建時(shí)的細(xì)節(jié)分析

你可以慢慢拆解上面的每一步尿这,單獨(dú)看看每一個(gè)組件大體是作什么的簇抵,這個(gè)就是細(xì)節(jié)的研究,可以一步一步來(lái)射众。

你可以研究下ResourceLoader 是個(gè)啥碟摆? 你可以看它的類注解后可以發(fā)現(xiàn),這個(gè)ResourceLoader 類負(fù)責(zé)使用ClassLoader加載ClassPath下的class和各種配置文件的叨橱。(如果你不知道JVM的ClassLoader機(jī)制典蜕,主要加載什么,可以自己去baidu罗洗、google了解下)嘉裤。這里你可以進(jìn)一步思考下,它設(shè)計(jì)成了一個(gè)接口栖博,可以實(shí)現(xiàn)不同的類加載器來(lái)加載資源。

webApplicationType 如何被推斷的厢洞?就是根據(jù)幾個(gè)靜態(tài)變量定義的類全限定名稱仇让,根據(jù)classPath下是否存在對(duì)應(yīng)的類典奉,來(lái)推斷出類型,使用了web-starter丧叽。默認(rèn)推斷出為Servlet類型的應(yīng)用卫玖。

至于primarySources、mainApplicationClass這個(gè)兩個(gè)變量記錄了LearnSpringBootApplication.class, 大體是為了之后掃描自動(dòng)配置等考慮的踊淳,表示從什么包名的哪一個(gè)類下啟動(dòng)的假瞬。

最后兩個(gè)集合變量Initializer和Listener如何設(shè)置的,這塊比較值得研究下迂尝。

基本原理是通過ClassLoader掃描了classPath下所有META-INF/spring.factories這個(gè)目錄中的文件脱茉,通過指定的factoryType,也就是接口名稱垄开,獲取對(duì)應(yīng)的所有實(shí)現(xiàn)類琴许,并且實(shí)例化成對(duì)象,返回成一個(gè)list列表溉躲。

比如factoryType=ApplicationContextInitializer 就返回這個(gè)接口在META-INF/spring.factories定義的所有的實(shí)現(xiàn)類榜田,并實(shí)例化為一個(gè)列表List ApplicationContextInitializer 。

ApplicationListener同理锻梳,獲取到了List ApplicationListener一個(gè)集合箭券。

這里面其實(shí)有很多細(xì)節(jié),使用了類加載器疑枯、緩存機(jī)制辩块,反射機(jī)制等,有興趣的同學(xué)可以仔細(xì)研究下神汹。

這里以我們抓大放小思想庆捺,概括成一句話:通過工具方法通過classLoader獲取classPath指定位置某個(gè)接口所有實(shí)現(xiàn)類的實(shí)例對(duì)象列表。

這里獲取的是ApplicationContextInitializer屁魏、ApplicationListener這兩個(gè)接口的實(shí)例對(duì)象列表滔以。

細(xì)節(jié)中可以學(xué)到知識(shí),脈絡(luò)中一樣可以學(xué)到知識(shí)氓拼,這個(gè)思想你一定要慢慢有你画。抓大放小的意思,更多的是讓你知道重點(diǎn)和關(guān)鍵點(diǎn)桃漾,而不是讓你丟棄細(xì)節(jié)坏匪,這兩者并不沖突,這個(gè)一定要注意撬统。

最后這里細(xì)節(jié)分析适滓,畫一個(gè)簡(jiǎn)單組件圖小結(jié)下:

file

SpringApplication Run方法的脈絡(luò)分析

熟悉了SpringApplication 的創(chuàng)建,接著我們?cè)摲治鏊膔un方法了恋追。

其實(shí)之前一節(jié)凭迹,我們介紹過SpringApplication 的啟動(dòng)流程罚屋。就是高度概括了run方法的核心脈絡(luò),run方法的核心其實(shí)核心就是下圖藍(lán)色的部分:

file

run方法脈絡(luò)可以主要概括為:

1)自動(dòng)裝配配置

2)Spring容器的創(chuàng)建

3)web容器啟動(dòng)(Tomcat的啟動(dòng))

然而在run方法的執(zhí)行過程嗅绸,肯定不會(huì)這么簡(jiǎn)單脾猛,過程中還摻雜了很多雜七雜八的邏輯,其中有意思的擴(kuò)展點(diǎn)鱼鸠,也有值得吐槽的坑猛拴。這是每個(gè)框架都會(huì)有的優(yōu)勢(shì)劣勢(shì)吧。我們先大體摸一下run方法的脈絡(luò)蚀狰,給大家介紹幾個(gè)術(shù)語(yǔ)愉昆,不然之后可能會(huì)看不懂代碼細(xì)節(jié)。

SpringApplication Run方法的脈絡(luò)進(jìn)一步分析

要想進(jìn)一步分析run方法的脈絡(luò)造锅,首先需要熟悉幾個(gè)術(shù)語(yǔ)撼唾,就有點(diǎn)像DDD的通用語(yǔ)言似的,懂了這些語(yǔ)言哥蔚,理解SpringBoot和Spring才會(huì)更得心應(yīng)手倒谷。

術(shù)語(yǔ)普及Context/BeanFactory/Environment

ConfigurableApplicationContext,容器通常稱為ApplicationContext或者BeanFactory糙箍,context也簡(jiǎn)稱為容器渤愁。ApplicationContext包裝了BeanFactory,封裝更高級(jí)的API而已深夯。

ConfigurableEnvironment 抖格,是配置文件的抽象,有關(guān)什么properties或者yml等配置文件的key-value值咕晋,都會(huì)封裝成這個(gè)類的某個(gè)實(shí)現(xiàn)類雹拄。

熟悉了這些術(shù)語(yǔ)后,我們看一起看下SpringApplication 的run方法代碼掌呜。

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);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            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;
    }

上面的代碼滓玖,主要就是執(zhí)行了一堆方法,可以從方法名字看出质蕉,都是圍繞Context势篡、Environment這些術(shù)語(yǔ)。也就是圍繞容器和配置文件組織的邏輯模暗。

在整個(gè)Spring容器的創(chuàng)建禁悠,刷新,刷新之后穿插了很多邏輯兑宇。

另外碍侦,SpringBoot整個(gè)run方法中有幾個(gè)很關(guān)鍵擴(kuò)展點(diǎn),設(shè)計(jì)SpringApplicationRunListeners、Runners等擴(kuò)展入口祝钢。容器創(chuàng)建比规、刷新等也要各自的擴(kuò)展點(diǎn),對(duì)容器的增強(qiáng)擴(kuò)展拦英,如beanFactoryPostProcessor,對(duì)Bean的增加擴(kuò)展测秸,如beanPostProcessor疤估。然而這些都是后話了。

我直接用一張圖給大家概括了霎冯,上面run方法脈絡(luò):

(*黑色是直觀的看出來(lái)的擴(kuò)展邏輯铃拇,白色是run方法每個(gè)方法的字面理解,只是每一步有很多擴(kuò)展點(diǎn)和做的事情比較多沈撞,讓你感覺會(huì)有點(diǎn)云里霧里的慷荔。藍(lán)色部分,概括了核心邏輯缠俺。也就是SpringBoot啟動(dòng)显晶,說(shuō)白了我們核心就是要找到這三點(diǎn):自動(dòng)裝配配置、Spring容器的創(chuàng)建壹士、web容器啟動(dòng)磷雇。)

file

這時(shí)候你一定要學(xué)會(huì)抓大放小的思想,之后帶著這3個(gè)關(guān)鍵步驟躏救,去理解SpringBoot唯笙,其他的實(shí)現(xiàn)可以單獨(dú)來(lái)研究分析它的設(shè)計(jì)思路,比如各個(gè)擴(kuò)展點(diǎn)的設(shè)計(jì)是如何考慮的盒使,我們可以參考借鑒哪一些崩掘。這才是學(xué)習(xí)SpringBoot最最該學(xué)習(xí)的。

概括下就是少办,當(dāng)一個(gè)技術(shù)看著比較復(fù)雜時(shí)苞慢,你應(yīng)該順著核心脈絡(luò)理解原理,學(xué)習(xí)各個(gè)細(xì)節(jié)的亮點(diǎn)設(shè)計(jì)思想凡泣。不要陷入某一個(gè)細(xì)節(jié)枉疼,多思考才最重要。大家一定要記住這一點(diǎn)鞋拟,在后續(xù)的成長(zhǎng)記中骂维,我會(huì)逐步帶大家體驗(yàn)這一點(diǎn)的。

小結(jié)

好了贺纲,簡(jiǎn)單小結(jié)下航闺。

主要思想學(xué)習(xí)了:

1)先脈絡(luò)后細(xì)節(jié)的思想,抓大放小的思想,排除不重要的潦刃,分析最主要的侮措。

2)細(xì)節(jié)中可以學(xué)到知識(shí),脈絡(luò)中一樣可以學(xué)到知識(shí)乖杠,這個(gè)思想你一定要慢慢有分扎。抓大放小的意思,更多的是讓你知道重點(diǎn)和關(guān)鍵點(diǎn)胧洒,而不是讓你丟棄細(xì)節(jié)畏吓,這兩者并不沖突,這個(gè)一定要注意卫漫。

3)多思考才最重要菲饼。順著核心脈絡(luò)理解原理,學(xué)習(xí)各個(gè)細(xì)節(jié)的亮點(diǎn)設(shè)計(jì)思想列赎,千萬(wàn)不能陷入知識(shí)本身宏悦。

主要知識(shí)學(xué)習(xí)了:

今天我們主要看了下SpringApplication的創(chuàng)建,它的核心組件有哪些包吝,創(chuàng)建后執(zhí)行的run方法饼煞,到底做了些什么,脈絡(luò)是怎么樣的漏策。

熟悉了這些脈絡(luò)派哲,剩下的就簡(jiǎn)單了,逐步分析每個(gè)細(xì)節(jié)掺喻,看看每個(gè)細(xì)節(jié)有些值得我們學(xué)習(xí)的點(diǎn)芭届,又有哪一些不太適合的點(diǎn)。

我們下期再見感耙!

本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布褂乍!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市即硼,隨后出現(xiàn)的幾起案子笨使,更是在濱河造成了極大的恐慌升熊,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異裕寨,居然都是意外死亡攒读,警方通過查閱死者的電腦和手機(jī)廓旬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門凿蒜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人绝编,你說(shuō)我怎么就攤上這事僻澎∶蔡ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵窟勃,是天一觀的道長(zhǎng)祖乳。 經(jīng)常有香客問我,道長(zhǎng)秉氧,這世上最難降的妖魔是什么眷昆? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮汁咏,結(jié)果婚禮上隙赁,老公的妹妹穿的比我還像新娘。我一直安慰自己梆暖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布掂骏。 她就那樣靜靜地躺著轰驳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弟灼。 梳的紋絲不亂的頭發(fā)上级解,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音田绑,去河邊找鬼勤哗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掩驱,可吹牛的內(nèi)容都是我干的芒划。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼欧穴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼民逼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起涮帘,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拼苍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后调缨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疮鲫,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年弦叶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俊犯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡湾蔓,死狀恐怖瘫析,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤贬循,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布咸包,位于F島的核電站,受9級(jí)特大地震影響杖虾,放射性物質(zhì)發(fā)生泄漏烂瘫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一奇适、第九天 我趴在偏房一處隱蔽的房頂上張望坟比。 院中可真熱鬧,春花似錦嚷往、人聲如沸葛账。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)籍琳。三九已至,卻和暖如春贷祈,著一層夾襖步出監(jiān)牢的瞬間趋急,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工势誊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呜达,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓粟耻,卻偏偏與公主長(zhǎng)得像查近,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子勋颖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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