Spring Boot 啓動(一) SpringApplication 分析

Spring Boot 啓動(一) SpringApplication 分析

Spring 系列目錄(http://www.javashuo.com/article/p-kqecupyl-bm.html)html

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

本節重點分析 Spring Boot(2.1.3) 的 SpringApplication#run 方法是如何啓動 Spring 容器。run 方法最終調用 new SpringApplication(primarySources).run(args)java

1、SpringApplication 初始化

1.1 重要的屬性說明

// 1. 配置類,primarySources 是 run 方法傳入的
private Set<Class<?>> primarySources;
private Set<String> sources = new LinkedHashSet<>();

// 2. main 方法所在的啓動類,日誌輸出用
private Class<?> mainApplicationClass;

// 3.environment 環境配置相關,addCommandLineProperties 添加 main 方法的命令行參數到 environment
private boolean addCommandLineProperties = true;
private boolean addConversionService = true;
private Map<String, Object> defaultProperties;
private Set<String> additionalProfiles = new HashSet<>();
private boolean isCustomEnvironment = false;

// 4. webmvc、webflux、非web 對應的 ApplicationContext 不一樣
private Class<? extends ConfigurableApplicationContext> applicationContextClass;
private WebApplicationType webApplicationType;

// 5. 經過 spring.factories 配置
private List<ApplicationContextInitializer<?>> initializers;
private List<ApplicationListener<?>> listeners;

1.2 SpringApplication 初始化

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 1. primarySources 爲配置類
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 2. 根據加載的 jar 推斷是 web、webflux、非web
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 3. 加載 ApplicationContextInitializer 到 initializers 中。 spring.factories
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    // 4. 加載監聽器到 listeners 中。 spring.factories
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 5. mainApplicationClass 啓動類,即根據 new RuntimeException().getStackTrace() 
    //    棧信息推斷 main 方法所在的類,本例中即爲 MyApplication,用於輸出日誌用
    this.mainApplicationClass = deduceMainApplicationClass();
}

在構造方法中重點關注第三步和第四步,經過 Spring 的 SPI 技術(相似 JDK 的 ServiceLoader,在 Spring 中爲 SpringFactoriesLoader)向容器中注入在 spring.factories 中配置的組件:web

  • 第三步:注入 ApplicationContextInitializer 的配置類。Spring Boot 默認組裝了 6 個實例,spring-boot-2.1.3.RELEASE.jar 下 4 個,spring-boot-autoconfigure-2.1.3.RELEASE.jar 下 2 個。spring

    0 = {DelegatingApplicationContextInitializer@1866} 
    1 = {SharedMetadataReaderFactoryContextInitializer@1867} 
    2 = {ContextIdApplicationContextInitializer@1868} 
    3 = {ConfigurationWarningsApplicationContextInitializer@1869} 
    4 = {ServerPortInfoApplicationContextInitializer@1870} 
    5 = {ConditionEvaluationReportLoggingListener@1871}
  • 第四步:注入監聽器,Spring 是基於事件驅動的,如配置文件的加載。Spring Boot 默認組裝了 10 個實例,spring-boot-2.1.3.RELEASE.jar 下 9 個,spring-boot-autoconfigure-2.1.3.RELEASE.jar 下 1 個。mvc

    0 = {ConfigFileApplicationListener@1771} 
    1 = {AnsiOutputApplicationListener@1772} 
    2 = {LoggingApplicationListener@1773} 
    3 = {ClasspathLoggingApplicationListener@1774} 
    4 = {BackgroundPreinitializer@1775} 
    5 = {DelegatingApplicationListener@1776} 
    6 = {ParentContextCloserApplicationListener@1777} 
    7 = {ClearCachesApplicationListener@1778} 
    8 = {FileEncodingApplicationListener@1779} 
    9 = {LiquibaseServiceLocatorApplicationListener@1780}

那 Spring 是如何保證這些組件的執行順序的呢?在 getSpringFactoriesInstances 獲取全部的實例後都會進行排序 AnnotationAwareOrderComparator.sort(instances)。 詳見:http://www.javashuo.com/article/p-xjjqneke-dc.htmlapp

2、run 方法主要流程分析

run 方法主要是啓動 ApplicationContext 容器,省略了一些非必須的代碼。dom

public ConfigurableApplicationContext run(String... args) {
    ConfigurableApplicationContext context = null;
    // 1. listeners 用戶監聽容器的運行,默認實現爲 EventPublishingRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 2. 初始化環境變量 environment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 3. 僅僅實例化對應的 ApplicationContext,尚未任何配置
        context = createApplicationContext();
        // 4. 配置 context,爲刷新容器作好準備
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 5. AbstractApplicationContext#refresh
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        listeners.started(context);
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    } catch (Throwable ex) {
        throw new IllegalStateException(ex);
    }
    return context;
}
  1. getRunListeners 初始化 SpringApplicationRunListeners,也是經過 spring.factories 配置 EventPublishingRunListener。SpringApplicationRunListeners 監聽了容器啓動、環境準備等事件。ide

  2. prepareEnvironment 初始化環境變量 environment,根據是不是 web 程序啓動不一樣的 Environment 實現。同時將 ①配置的默認數據源 defaultProperties;②main 方法的參數;③application.properties 配置文件看成 Environment 的數據源。spring-boot

  3. createApplicationContext 實例化 ApplicationContextpost

  4. prepareContext 配置 ApplicationContext

  5. refreshContext 刷新 ApplicationContext

2.1 getRunListeners - 初始化 SpringApplicationRunListeners

SpringApplicationRunListeners 默認實現爲 EventPublishingRunListener。注意之因此不用 ApplicationContext 直接觸發事件,是由於只有到第 4 步 contextLoaded 以後容器的初始化工做才完成,此時才能用 context.publishEvent() 觸發相應的事件。

public interface SpringApplicationRunListener {
    // 1. 調用 run 方法後首先觸發 starting 事件
    void starting();
    // 2. prepareEnvironment。初始化 environment 時調用,
    void environmentPrepared(ConfigurableEnvironment environment);
    // 3. prepareContext 開始時調用
    void contextPrepared(ConfigurableApplicationContext context);
    // 4. prepareContext 完成時調用
    void contextLoaded(ConfigurableApplicationContext context);
    // 5. refreshContext 後調用
    void started(ConfigurableApplicationContext context);
    // 6. started 後調用,run 方法調用完成
    void running(ConfigurableApplicationContext context);
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

2.2 prepareEnvironment - 初始化環境變量 environment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 1. 根據 webApplicationType 建立相應的 Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 2. 配置 Environment,主要有三點:一是 ConversionService;二是數據源,包括命令行參數;三是 Profiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 3. 激活 environmentPrepared 事件,主要是加載 application.yml 等配置文件
    //    ConfigFileApplicationListener#ApplicationEnvironmentPreparedEvent
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    // ??? 之後再研究
    ConfigurationPropertySources.attach(environment);
    return environment;
}

加載後的數據源以下:

0 = {SimpleCommandLinePropertySource@2697} "SimpleCommandLinePropertySource {name='commandLineArgs'}"
1 = {PropertySource$StubPropertySource@2698} "StubPropertySource {name='servletConfigInitParams'}"
2 = {PropertySource$StubPropertySource@2699} "StubPropertySource {name='servletContextInitParams'}"
3 = {MapPropertySource@2700} "MapPropertySource {name='systemProperties'}"
4 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor$OriginAwareSystemEnvironmentPropertySource@2701} "OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}"
5 = {RandomValuePropertySource@2702} "RandomValuePropertySource {name='random'}"
6 = {OriginTrackedMapPropertySource@2703} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}"

執行加載的過程以下:

  1. servletConfigInitParams 和 servletContextInitParams 是 web 項目獨有,systemEnvironment 和 systemEnvironment 這些都在初始化 environment 注入。
  2. commandLineArgs 是 main 方法命令行參數,在 configureEnvironment 注入。
  3. applicationConfig 是配置文件,在 environmentPrepared 時經過 ConfigFileApplicationListener#ApplicationEnvironmentPreparedEvent 事件注入。

2.3 createApplicationContext - 實例化 ApplicationContext

沒什麼可說的,略過

2.4 prepareContext- 配置 ApplicationContext

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 1. 爲 context 注入基本的組件
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // 2. List<ApplicationContextInitializer<?>> initializers 在初始化的時候已經注入
    applyInitializers(context);
    // 3. 觸發 contextPrepared 事件
    listeners.contextPrepared(context);
    // 4. 配置 beanFactory
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // 5. load 方法加載 BeanDefinition
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    // 6. 觸發 contextLoaded 事件,此時 context 準備工做已經完成
    listeners.contextLoaded(context);
}

prepareContext 主要是配置 context 和 beanFactory。其中最重要的方法是 load(context, sources.toArray(new Object[0])) 方法,向容器中加載 BeanDefinition。sources 指的是 Spring 的配置類,默認爲 this.primarySources 即 SpringApplication.run(Application.class, args) 中的配置類 Application。

下面主要看一下 load 是如何加載 BeanDefinition 的。

protected void load(ApplicationContext context, Object[] sources) {
    BeanDefinitionLoader loader = createBeanDefinitionLoader(
            getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }
    loader.load();
}

protected BeanDefinitionLoader createBeanDefinitionLoader( 
        BeanDefinitionRegistry registry, Object[] sources) {
    return new BeanDefinitionLoader(registry, sources);
}

load 將加載 BeanDefinitionLoader 委託給了 BeanDefinitionLoader#load() 方法,其中 sources 即爲配置類。

2.5 refreshContext - 刷新 ApplicationContext

沒什麼可說的,見 AbstractApplicationContext#refresh(http://www.javashuo.com/article/p-tpkmaktp-ed.html)


天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索