天天看點

Spring Boot-Spring Boot應用啟動

一、SpringApplication初始化

SpringBoot整個啟動流程分為兩個步驟:初始化一個SpringApplication對象、執行該對象的run方法。看下SpringApplication的初始化流程,SpringApplication的構造方法中調用initialize(Object[] sources)方法,其代碼如下:

private void initialize(Object[] sources) {
     if (sources != null && sources.length > 0) {
         this.sources.addAll(Arrays.asList(sources));
     }
     // 判斷是否是Web項目
     this.webEnvironment = deduceWebEnvironment();
     setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
     setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
     // 找到入口類
     this.mainApplicationClass = deduceMainApplicationClass();
}
           

初始化流程中最重要的就是通過SpringFactoriesLoader找到

spring.factories

檔案中配置的

ApplicationContextInitializer

ApplicationListener

兩個接口的實作類名稱,以便後期構造相應的執行個體。

ApplicationContextInitializer

的主要目的是在

ConfigurableApplicationContext

做refresh之前,對ConfigurableApplicationContext執行個體做進一步的設定或處理。ConfigurableApplicationContext繼承自ApplicationContext,其主要提供了對ApplicationContext進行設定的能力。

實作一個ApplicationContextInitializer非常簡單,因為它隻有一個方法,但大多數情況下我們沒有必要自定義一個ApplicationContextInitializer,即便是Spring Boot架構,它預設也隻是注冊了兩個實作,畢竟Spring的容器已經非常成熟和穩定,你沒有必要來改變它。

ApplicationListener

的目的就沒什麼好說的了,它是Spring架構對Java事件監聽機制的一種架構實作,具體内容在前文Spring事件監聽機制這個小節有詳細講解。這裡主要說說,如果你想為Spring Boot應用添加監聽器,該如何實作?

Spring Boot提供兩種方式來添加自定義監聽器:

  • 通過

    SpringApplication.addListeners(ApplicationListener<?>... listeners)

    或者

    SpringApplication.setListeners(Collection<? extends ApplicationListener<?>> listeners)

    兩個方法來添加一個或者多個自定義監聽器
  • 既然SpringApplication的初始化流程中已經從

    spring.factories

    中擷取到

    ApplicationListener

    的實作類,那麼我們直接在自己的jar包的

    META-INF/spring.factories

    檔案中新增配置即可:
org.springframework.context.ApplicationListener=\
cn.moondev.listeners.xxxxListener\
           

二、Spring Boot啟動流程

Spring Boot應用的整個啟動流程都封裝在SpringApplication.run方法中,其整個流程真的是太長太長了,但本質上就是在Spring容器啟動的基礎上做了大量的擴充,按照這個思路來看看源碼:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        // ①
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            // ②
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
            // ③
            Banner printedBanner = printBanner(environment);
            // ④
            context = createApplicationContext();
            // ⑤
            analyzers = new FailureAnalyzers(context);
            // ⑥
            prepareContext(context, environment, listeners, applicationArguments,printedBanner);
            // ⑦ 
            refreshContext(context);
            // ⑧
            afterRefresh(context, applicationArguments);
            // ⑨
            listeners.finished(context, null);
            stopWatch.stop();
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
}
           

① 通過SpringFactoriesLoader查找并加載所有的

SpringApplicationRunListeners

,通過調用starting()方法通知所有的SpringApplicationRunListeners:應用開始啟動了。SpringApplicationRunListeners其本質上就是一個事件釋出者,它在SpringBoot應用啟動的不同時間點釋出不同應用事件類型(ApplicationEvent),如果有哪些事件監聽者(ApplicationListener)對這些事件感興趣,則可以接收并且處理。還記得初始化流程中,SpringApplication加載了一系列ApplicationListener嗎?這個啟動流程中沒有發現有釋出事件的代碼,其實都已經在SpringApplicationRunListeners這兒實作了。

簡單的分析一下其實作流程,首先看下SpringApplicationRunListener的源碼:

public interface SpringApplicationRunListener {

    // 運作run方法時立即調用此方法,可以使用者非常早期的初始化工作
    void starting();
    
    // Environment準備好後,并且ApplicationContext建立之前調用
    void environmentPrepared(ConfigurableEnvironment environment);

    // ApplicationContext建立好後立即調用
    void contextPrepared(ConfigurableApplicationContext context);

    // ApplicationContext加載完成,在refresh之前調用
    void contextLoaded(ConfigurableApplicationContext context);

    // 當run方法結束之前調用
    void finished(ConfigurableApplicationContext context, Throwable exception);

}
           

SpringApplicationRunListener隻有一個實作類:

EventPublishingRunListener

。上面的代碼隻會擷取到一個EventPublishingRunListener的執行個體,我們來看看starting()方法的内容:

public void starting() {
    // 釋出一個ApplicationStartedEvent
    this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
}
           

順着這個邏輯,你可以在

prepareEnvironment()

方法的源碼中找到

listeners.environmentPrepared(environment);

即SpringApplicationRunListener接口的第二個方法,那不出你所料,

environmentPrepared()

又釋出了另外一個事件

ApplicationEnvironmentPreparedEvent

。接下來會發生什麼,就不用我多說了吧。

② 建立并配置目前應用将要使用的

Environment

,Environment用于描述應用程式目前的運作環境,其抽象了兩個方面的内容:配置檔案(profile)和屬性(properties),開發經驗豐富的同學對這兩個東西一定不會陌生:不同的環境(eg:生産環境、預釋出環境)可以使用不同的配置檔案,而屬性則可以從配置檔案、環境變量、指令行參數等來源擷取。是以,當Environment準備好後,在整個應用的任何時候,都可以從Environment中擷取資源。

總結起來,主要完成以下幾件事:

  • 判斷Environment是否存在,不存在就建立(如果是web項目就建立

    StandardServletEnvironment

    ,否則建立

    StandardEnvironment

  • 配置Environment:配置profile以及properties
  • 調用SpringApplicationRunListener的

    environmentPrepared()

    方法,通知事件監聽者:應用的Environment已經準備好

③、SpringBoot應用在啟動時會輸出這樣的東西:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.6.RELEASE)
           

④、根據是否是web項目,來建立不同的ApplicationContext容器。

⑤、建立一系列

FailureAnalyzer

,建立流程依然是通過SpringFactoriesLoader擷取到所有實作FailureAnalyzer接口的class,然後在建立對應的執行個體。FailureAnalyzer用于分析故障并提供相關診斷資訊。

⑥、初始化ApplicationContext,主要完成以下工作:

  • 将準備好的Environment設定給ApplicationContext
  • 周遊調用所有的ApplicationContextInitializer的

    initialize()

    方法來對已經建立好的ApplicationContext進行進一步的處理
  • 調用SpringApplicationRunListener的

    contextPrepared()

    方法,通知所有的監聽者:ApplicationContext已經準備完畢
  • 将所有的bean加載到容器中
  • 調用SpringApplicationRunListener的

    contextLoaded()

    方法,通知所有的監聽者:ApplicationContext已經裝載完畢

⑦、調用ApplicationContext的

refresh()

方法,完成IoC容器可用的最後一道工序。從名字上了解為重新整理容器,那何為重新整理?就是插手容器的啟動,聯系一下第一小節的内容。那如何重新整理呢?且看下面代碼:

// 摘自refresh()方法中一句代碼
invokeBeanFactoryPostProcessors(beanFactory);
           

看看這個方法的實作:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    ......
}
           

擷取到所有的

BeanFactoryPostProcessor

來對容器做一些額外的操作。BeanFactoryPostProcessor允許我們在容器執行個體化相應對象之前,對注冊到容器的BeanDefinition所儲存的資訊做一些額外的操作。這裡的getBeanFactoryPostProcessors()方法可以擷取到3個Processor:

ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
           

不是有那麼多BeanFactoryPostProcessor的實作類,為什麼這兒隻有這3個?因為在初始化流程擷取到的各種ApplicationContextInitializer和ApplicationListener中,隻有上文3個做了類似于如下操作:

public void initialize(ConfigurableApplicationContext context) {
    context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}
           

然後你就可以進入到

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()

方法了,這個方法除了會周遊上面的3個BeanFactoryPostProcessor處理外,還會擷取類型為

BeanDefinitionRegistryPostProcessor

的bean:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

,對應的Class為

ConfigurationClassPostProcessor

ConfigurationClassPostProcessor

用于解析處理各種注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。當處理

@import

注解的時候,就會調用

EnableAutoConfigurationImportSelector.selectImports()

來完成自動配置功能

⑧、查找目前context中是否注冊有CommandLineRunner和ApplicationRunner,如果有則周遊執行它們。

⑨、執行所有SpringApplicationRunListener的finished()方法。

這就是Spring Boot的整個啟動流程,其核心就是在Spring容器初始化并啟動的基礎上加入各種擴充點,這些擴充點包括:ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等。你對整個流程的細節不必太過關注,甚至沒弄明白也沒有關系,你隻要了解這些擴充點是在何時如何工作的,能讓它們為你所用即可。

内容轉自:https://www.jianshu.com/p/83693d3d0a65