Spring Boot啟動過程源碼分析

由于簡書的markdown不支持目錄結構,更好的閱讀體驗可以查看對應的個人blog: https://buaazhangyk.github.io/2018/07/02/spring-boot-1/

0. 前言

開始之前肌蜻,舉一個使用spring boot啟動web應用的代碼示例:

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

通過上述代碼可以看到蒋搜,Spring Boot 中應用啟動的核心入口在SpringApplication這個類中完成豆挽。繼續(xù)查看在SpringApplicationrun方法內(nèi)部執(zhí)行中券盅,主要分為兩步:1.初始化創(chuàng)建一個SpringApplication,2.然后執(zhí)行run(String... args)方法對Application進行啟動娘侍。

public staticConfigurableApplicationContext run(Class[] primarySources, String[] args) {
    return newSpringApplication(primarySources).run(args);
}

由此憾筏,本文對spring boot啟動過程的分析也會從這兩部分進行展開花鹅。1)SpringApplication的初始化部分刨肃; 2)SpringApplication的run執(zhí)行部分。

1. SpringApplication的初始化

1.1 SpringApplication類私有變量

下邊的圖簡單描述了SpringApplication類所包含的一些私有變量黄痪。后續(xù)會結合類的構造函數(shù)來分析其中的重要私有變量及其左右锻狗。

image.png

1.2 SpringApplication構造函數(shù)

結合Class的構造函數(shù)來重點看一下下面幾個變量的作用以及如何進行初始化的轻纪。

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 = deduceWebApplicationType();
    setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

1.2.1 ResourceLoader

表示Spring中用來加載資源的資源加載器刻帚。

1.2.2 webApplicationType

代表這個SpringApplication的類型崇众,主要包括三個類型:NONE / SERVLET / REACTIVE

NONE: The application should not run as a web application and should not start an embedded web server.

SERVLET: The application should run as a servlet-based web application and should start anembedded servlet
web server.

REACTIVE: The application should run as a reactive web application and should start anembedded reactive web server.

SpringBoot是怎么知道究竟是那種類型的ApplicationType的呢顷歌?實現(xiàn)的代碼在方法deduceWebApplicationType()中。

    private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

在這段代碼里一個核心的方法調(diào)用是 ClassUtils.isPresent(String className,@NullableClassLoader classLoader),
這個方法是判斷參數(shù)中的className是否存在并可以加載成功芹扭。由此可見WebApplicationType類型的判斷取決于引入的jar包舱卡。
其中,
REACTIVE_WEB_ENVIRONMENT_CLASS 對應的類為 org.springframework.web.reactive
.DispatcherHandler
队萤, 對應的package是spring-webflux

MVC_WEB_ENVIRONMENT_CLASS對應的類為org.springframework.web.servlet.DispatcherServlet 要尔, 對應的package是 spring-webmvc

WEB_ENVIRONMENT_CLASSES 對應的類為{"javax.servlet.Servlet","org.springframework.web.context
.ConfigurableWebApplicationContext” }
赵辕,對應的package是servlet-apispring-web

1.2.3 ApplicationContextInitializer

用來在對ApplicationContext進行refresh操作之前對Application context進行一些初始化操作。

Callback interface for initializing a Spring {@ConfigurableApplicationContext} prior to being
{@ConfigurableApplicationContext#refresh()} refreshed.
Typically used within web applications that require some programmatic initialization of the application context.
For example, registering property sources or activating profiles against the
{@ConfigurableApplicationContext#getEnvironment()}
context's environment. See {@ContextLoader} and {@FrameworkServlet} support for declaring a "contextInitializerClasses" context-param and init-param, respectively.

通過查看代碼熬词,我們可以看到ApplicationContextInitializer的獲取是通過調(diào)用 getSpringFactoriesInstances(Class<T> type)
方法得到的互拾,這個方法實際是去尋找指定Class類型的類并將其實例化返回颜矿。那具體是從哪里找呢骑疆? 會在后邊的小節(jié)單獨分析一下

1.2.4 ApplicationListener

基于觀察者模式的Application的事件監(jiān)聽器替废。將ApplicationListener注冊到ApplicationContext中椎镣,當有對應事件發(fā)生時,監(jiān)聽器會被調(diào)用

Interface to be implemented by application event listeners.
Based on the standard {@java.util.EventListener} interface for the Observer design pattern.
As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When
registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.

與獲取ApplicationContextInitializer的過程一直冷守, ApplicationListener的獲取也是通過調(diào)用getSpringFactoriesInstances(Class<T> type)實現(xiàn)拍摇。

1.2.5 mainApplicationClass

啟動應用程序的main class.通過分析當前的程序堆棧信息獲取亮钦。

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

1.2.6 關于getSpringFactoriesInstances

2.SpringApplication的run

2.1 整體流程

run方法是SpringApplication的核心方法蜂莉,在這個方法內(nèi)部完成了 系統(tǒng)屬性的注入,Runner的執(zhí)行堪唐,
創(chuàng)建巡语、準備以及refresh整個ApplicationContext 核心流程。大致可以將整個run方法歸納分解成6個步驟淮菠。

[圖片上傳失敗...(image-4fd544-1531105646640)]

2.2 細節(jié)分析

下邊對這六個步驟進行詳細的分析和解讀男公。


image.png

2.2.1 SpringApplicationRunListeners

SpringApplicationRunListener是事件監(jiān)聽器,用來監(jiān)聽SpringApplication
的啟動過程合陵,監(jiān)聽到事件發(fā)生時進行一些回調(diào)操作枢赔。通過下邊的代碼可以看到獲取的核心方法是getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)完成的

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

2.2.2 Prepare Environment

配置Application的環(huán)境,主要是一些property拥知,這些屬性會在創(chuàng)建ApplicationContext以及Referesh的時候起到左右踏拜。

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

核心流程:

  1. 創(chuàng)建一個ConfigurableEnvironment

  2. 配置ConfigurableEnvironment,主要配置PropertySource(包括defaultProperties和addCommandLineProperties)和Profile姻锁。

  3. 將environment綁定至SpringApplication

  4. Attach一個ConfigurationPropertySource至environment.

2.2.3 Create ApplicationContext

根據(jù)this.webApplicationType的類型來創(chuàng)建不同的ConfigurableApplicationContext。

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

2.2.4 Prepare Context

在 prepareContext的過程中,首先會運行一些初始化的流程笋妥,然后會注冊一些spring boot啟動所需要的bean挽鞠,加載一些初始的beans。

//1. Apply any relevant post processing the ApplicationContext
postProcessApplicationContext(context);
//2. Apply any {@linkApplicationContextInitializer}s to the context before it isrefreshed.
applyInitializers(context);
//3. Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
//4. Load beans into the application context.
Set sources = getAllSources();
load(context, sources.toArray(newObject[0]));

2.2.5 Refresh Context

對前邊兩步中創(chuàng)建和準備好的application context執(zhí)行refresh操作,這一部分的具體實現(xiàn)在spring-context包中潦蝇。本文不再具體分析攘乒。

2.2.6 Call Runner

主要是調(diào)用一下設置的一些ApplicationRunner和CommandLineRunner。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

3 總結

Spring boot實際上是對原有Spring Application啟動方式的一種革命。
在傳統(tǒng)的Spring Application中爽雄,程序啟動的方式是傳統(tǒng)的web容器(如tomcat、jetty)為入口乘盖,spring application作為webAppContext插入到web容器中侧漓。而在spring
boot的方式中,完全是以spring application為主纵揍,傳統(tǒng)的web容器作為插件嵌入到spring application中。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骨杂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子妒潭,更是在濱河造成了極大的恐慌雳灾,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嚎花,居然都是意外死亡,警方通過查閱死者的電腦和手機兵罢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裆赵,你說我怎么就攤上這事页藻》菡剩” “怎么了肚逸?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長血当。 經(jīng)常有香客問我臊旭,道長离熏,這世上最難降的妖魔是什么啥刻? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任娄涩,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘碎捺。我一直安慰自己,他們只是感情好雁竞,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布进栽。 她就那樣靜靜地躺著,像睡著了一般唠帝。 火紅的嫁衣襯著肌膚如雪禁荸。 梳的紋絲不亂的頭發(fā)上瑰妄,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天竹宋,我揣著相機與錄音,去河邊找鬼砂缩。 笑死,一個胖子當著我的面吹牛双吆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼苔巨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了悼尾?” 一聲冷哼從身側響起俯画,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娩怎,沒想到半個月后辣辫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谷遂,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡集晚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年莲绰,在試婚紗的時候發(fā)現(xiàn)自己被綠了辞友。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戳晌。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸿捧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泼菌,到底是詐尸還是另有隱情,我是刑警寧澤系任,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布霜旧,位于F島的核電站喉悴,受9級特大地震影響今魔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一睡蟋、第九天 我趴在偏房一處隱蔽的房頂上張望夭苗。 院中可真熱鬧,春花似錦、人聲如沸仔蝌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春远荠,著一層夾襖步出監(jiān)牢的瞬間盹兢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工宜咒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓吏颖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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