SpringBoot啟動過程

從 java main 方法啟動

SpringApplication 構(gòu)造方法

    @SuppressWarnings({ "unchecked", "rawtypes" })
    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(); // 推斷 web 應(yīng)用類型
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class)); // 獲取 spring 工廠實例
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

getSpringFactoriesInstances 源碼

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));            // 獲取指定類型的工廠名字
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,    // 根據(jù)名字、類型創(chuàng)建工廠實例
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

從源碼我們看出主要做了三件事:

  1. loadFactoryNames,加載指定類型的工廠名稱
    loadSpringFactories:
    a. 查找類路徑下全部的META-INF/spring.factories的URL
    b. 根據(jù)url加載全部的spring.factories中的屬性
    c. 將所有spring.factories中的值緩存到 SpringFactoriesLoader 的 cache 中,方便下次調(diào)用
  2. createSpringFactoriesInstances,創(chuàng)建指定類型的工廠實例,根據(jù)上面獲取的指定類型的工廠名稱列表來實例化工廠 bean,我們可以簡單的認(rèn)為通過反射來實例化
  3. 對工廠實例進(jìn)行排序,然后返回排序后的實例列表

構(gòu)造總結(jié):

  1. 構(gòu)造自身實例
  2. 推測 web 應(yīng)用類型,并賦值到屬性 webApplicationType
  3. 設(shè)置屬性 initializers 和 listeners 中途讀取了類路徑下所有 META-INF/spring.factories 的屬性,并緩存到了 SpringFactoriesLoader 的 cache 緩存中
  4. 推斷主類,并賦值到屬性 mainApplicationClass

run 方法

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    // 秒表,用于記錄啟動時間;記錄每個任務(wù)的時間,最后會輸出每個任務(wù)的總費時
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // spring應(yīng)用上下文,也就是我們所說的spring根容器
    ConfigurableApplicationContext context = null;
    // 自定義SpringApplication啟動錯誤的回調(diào)接口
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 設(shè)置jdk系統(tǒng)屬性java.awt.headless,默認(rèn)情況為true即開啟
    configureHeadlessProperty();
    //  KEY 1 - 獲取啟動時監(jiān)聽器
    SpringApplicationRunListeners listeners = getRunListeners(args)
    // 觸發(fā)啟動事件,啟動監(jiān)聽器會被調(diào)用,一共5個監(jiān)聽器被調(diào)用
    listeners.starting(); 
    try {
        // 參數(shù)封裝,也就是在命令行下啟動應(yīng)用帶的參數(shù),如--server.port=9000
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // KEY 2 - 準(zhǔn)備環(huán)境 1、加載外部化配置的資源到environment;2、觸發(fā)ApplicationEnvironmentPreparedEvent事件
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        // 配置spring.beaninfo.ignore,并添加到名叫systemProperties的PropertySource中;默認(rèn)為true即開啟
        configureIgnoreBeanInfo(environment);
        // 打印banner圖
        Banner printedBanner = printBanner(environment);
        // KEY 3 - 創(chuàng)建應(yīng)用上下文,并實例化了其三個屬性:reader、scanner和beanFactory
        context = createApplicationContext();
        // 獲取異常報道器,即加載spring.factories中的SpringBootExceptionReporter實現(xiàn)類
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        //  KEY 4 - 準(zhǔn)備上下文前置處理
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // KEY 5 - Spring上下文刷新
        refreshContext(context);
        // KEY 6 - Spring上下文后置處理
        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;
}

getRunListeners() 方法:返回一個新的SpringApplicationRunListeners實例對象
細(xì)看的話,這次是從SpringFactoriesLoader的cache中取SpringApplicationRunListener類型的類(全限定名),然后實例化后返回。說的簡單點,getRunListeners就是準(zhǔn)備好了運(yùn)行時監(jiān)聽器EventPublishingRunListener。
listeners.starting() 方法:構(gòu)建了一個ApplicationStartingEvent事件,并將其發(fā)布出去,對每個listener進(jìn)行invokeListener,調(diào)用過濾出的監(jiān)聽器


prepareEnvironment() 方法:

// 準(zhǔn)備環(huán)境
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment 創(chuàng)建和配置環(huán)境
    // 獲取或創(chuàng)建環(huán)境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置環(huán)境:配置PropertySources和activeProfiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // listeners環(huán)境準(zhǔn)備(就是廣播ApplicationEnvironmentPreparedEvent事件)
    listeners.environmentPrepared(environment);
    // 將環(huán)境綁定到SpringApplication
    bindToSpringApplication(environment);
    // 如果是非web環(huán)境,將環(huán)境轉(zhuǎn)換成StandardEnvironment
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    // 配置PropertySources對它自己的遞歸依賴
    ConfigurationPropertySources.attach(environment);
    return environment;
}

configureEnvironment() 方法:將配置任務(wù)按順序委托給 configurePropertySources 和 configureProfiles

configurePropertySources:注釋說明是增加、移除或者重排序應(yīng)用環(huán)境中的 PropertySource。就目前而言,如果有命令行參數(shù)則新增封裝命令行參數(shù)的PropertySource,并將它放到sources的第一位置。

configureProfiles:配置應(yīng)用環(huán)境中的哪些配置文件處于激活狀態(tài)(或默認(rèn)激活)??梢酝ㄟ^spring.profiles.active屬性在配置文件處理期間激活其他配置文件。說的簡單點就是設(shè)置哪些Profiles是激活的。

listeners.environmentPrepared(environment):這其中會初始化 PropertiesPropertySourceLoader 和 YamlPropertySourceLoader 這兩個加載器從 file:./config/,file:./,classpath:/config/,classpath:/ 路徑下加載配置文件,PropertiesPropertySourceLoader 加載配置文件 application.xml 和application.properties,YamlPropertySourceLoader 加載配置文件 application.yml 和application.yaml。目前我們之后 classpath:/ 路徑下有個 application.yml 配置文件,將其屬性配置封裝進(jìn)了一個名叫 applicationConfig:[classpath:/application.yml] 的 OriginTrackedMapPropertySource 中,并將此對象放到了 propertySourceList 的最后。

environmentPrepared 方法會觸發(fā)所有監(jiān)聽了 ApplicationEnvironmentPreparedEvent 事件的監(jiān)聽器,加載外部化配置資源到 environment,包括命令行參數(shù)、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties) 等;

bindToSpringApplication(environment):就是將 environment 綁定到 SpringApplication


createApplicationContext:根據(jù)SpringApplication的webApplicationType來實例化對應(yīng)的上下文,對其部分屬性:reader、scanner、beanFactory進(jìn)行了實例化;reader中實例化了屬性conditionEvaluator;scanner中添加了兩個AnnotationTypeFilter:一個針對@Component,一個針對@ManagedBean;beanFactory中注冊了8個注解配置處理器。


prepareContext:
1、將 context 中的 environment 替換成 SpringApplication 中創(chuàng)建的 environment
2、將SpringApplication中的 initializers 應(yīng)用到 context 中
設(shè)置application id,并將application id封裝成ContextId對象,注冊到beanFactory中
向context的beanFactoryPostProcessors中注冊了一個ConfigurationWarningsPostProcessor實例
向context的applicationListeners中注冊了一個ServerPortInfoApplicationContextInitializer實例
向context的beanFactoryPostProcessors中注冊了一個CachingMetadataReaderFactoryPostProcessor 實例
向context的applicationListeners中注冊了一個ConditionEvaluationReportListener實例
3、加載兩個單例bean到beanFactory中
向beanFactory中注冊了一個名叫springApplicationArguments的單例bean,該bean封裝了我們的命令行參數(shù);
向beanFactory中注冊了一個名叫springBootBanner的單例bean。
4、加載bean定義資源
5、將 SpringApplication 中的 listeners 注冊到 context 中,并廣播 ApplicationPreparedEvent 事件
總共11個 ApplicationListener 注冊到了 context 的 applicationListeners 中;
ApplicationPreparedEvent 事件的監(jiān)聽器一共做了兩件事:
+ 向 context 的 beanFactoryPostProcessors 中注冊了一個 PropertySourceOrderingPostProcessor 實例
+ 向 beanFactory 中注冊了一個名叫 springBootLoggingSystem 的單例 bean,也就是我們的日志系統(tǒng) bean
context 中主要是三個屬性增加了內(nèi)容:beanFactory、beanFactoryPostProcessors 和 applicationListeners


load:就是加載 bean 定義資源,支持4種方式:Class、Resource、Package和CharSequence。
Class:注解形式的 Bean 定義;AnnotatedBeanDefinitionReader 負(fù)責(zé)處理。
Resource:一般而言指的是 xml bean 配置文件,也就是我們在 spring 中常用的 xml 配置。說的簡單點就是:將 xml 的 bean 定義封裝成 BeanDefinition 并注冊到 beanFactory 的 BeanDefinitionMap 中;XmlBeanDefinitionReader 負(fù)責(zé)處理。
Package:以掃包的方式掃描bean定義; ClassPathBeanDefinitionScanner 負(fù)責(zé)處理。
CharSequence:以先后順序進(jìn)行匹配 Class、Resource 或 Package 進(jìn)行加載,誰匹配上了就用誰的處理方式處理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容