Spring-Cloud-Context模塊

簡介

SpringCloud這個框架本身是建立在SpringBoot基礎之上的,所以使用SpringCloud的方式與SpringBoot相仿。也是通過類似如下代碼進行啓動。

SpringApplication.run(XxxApplication.class, args);

其中 XxxApplication.class 類上也需要添加 @SpringBootApplication註解。
要使用SpringCloud框架,在pom文件中要確保引入 spring-cloud-starter 依賴包,spring-cloud-starter依賴如下的jar:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-context</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-commons</artifactId>
    </dependency>
</dependencies>

其中 spring-cloud-context-x.y.z.RELEASE.jarspring-cloud-commons-x.y.z.RELEASE.jar 下的 META-INF 目錄下都包含 spring.factories 文件,所以可以把這兩個jar看作是springCloud程序的入口

SpringCloud在構建上下文<即ApplicationContext實例>時,採用了Spring父子容器的設計,會在 SpringBoot構建的容器<後面稱之爲應用容器>之上創建一一父容器Bootstrap Application Context .

那麼SpringCloud設計出Bootstrap Application Context ,並把它作爲應用容器的父容器的目的是什麼呢?

因爲SpringCloud 作爲一個分佈式微服務框架,需要使用全局的配置中心,而配置中心的配置是可以提供給應用容器的,所以在應用容器初始化和實例化Bean之前需要先完成配置中心的實例化,這個任務就由Bootstrap Application Context 來完成,而配置中心的相關配置屬性就從 bootstrap.propertiesbootstrap.yml 文件中讀取。

但要注意的是,在Bootstrap Application Context 啓動工作完成之後,其從bootstrap.properties或bootstrap.yml文件中讀取的配置,是會被應用容器對應的application.properties或yml文件中的同名屬性覆蓋的。

從源碼角度分析上面的論述

1.代碼運行時還是從SpringApplication實例的run方法開始 ,此處會觸發 BootstrapApplicationListener 類中的代碼 , Bootstrap Application Context 的創建就是通過這個監聽器觸發的

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 此處會加載spring-cloud-context提供的監聽器org.springframework.cloud.bootstrap.BootstrapApplicationListener.class
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);            
        // 此處會發布ApplicationEnvironmentPreparedEvent事件,觸發BootstrapApplicationListener中的代碼
        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;
}

2.Bootstrap Application Context 的實例化 ,由BootstrapApplicationListener類的 onApplicationEvent方法觸發

public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();        
    // 可以通過環境變量 spring.cloud.bootstrap.enabled來禁止使用Bootstrap容器
    if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
        return;
    }
    // 由於Bootstrap容器在創建時還是會再次調用上面步驟1的代碼,還會再次觸發
    // BootstrapApplicationListener類這個方法,所以此處作個判斷,
    // 如果當前是Bootstrap容器的處理,則直接返回
    if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
        return;
    }
    ConfigurableApplicationContext context = null;        
    // 獲取配置文件的名字,默認爲bootstrap.properties或.yml ,並且這個名字可以通過 spring.cloud.bootstrap.name在環境中配置
    String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
    for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
         .getInitializers()) {             
        // 從相應jar中的spring.factories文件中讀取初始化器的配置類實例,如果這個實例類是
        // ParentContextApplicationContextInitializer類型,則直接從該類中獲取到父容器
        // 默認情況下,沒有提供這樣的類,下面這段代碼會跳過
        if (initializer instanceof ParentContextApplicationContextInitializer) {
            context = findBootstrapContext(
                (ParentContextApplicationContextInitializer) initializer,
                configName);
        }
    }
    if (context == null) {            // 此處分析見步驟3
        context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
        event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
    }

    apply(context, event.getSpringApplication(), environment);
}

3.bootstrap容器的創建

private ConfigurableApplicationContext bootstrapServiceContext(
    ConfigurableEnvironment environment, final SpringApplication application,
    String configName) {
    /**此處代碼主要是從各處獲取屬性配置,此處忽略**/
    // TODO: is it possible or sensible to share a ResourceLoader?
    SpringApplicationBuilder builder = new SpringApplicationBuilder()
        .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
        .environment(bootstrapEnvironment)
        // Don't use the default properties in this builder
        .registerShutdownHook(false).logStartupInfo(false)
        .web(WebApplicationType.NONE);        
    // SpringApplicationBuilder的作用:1.構建SpringApplication  2.構建ApplicationContext        
    // 這裏需要思考一下,SpringBoot在啓動時已經構建了一個SpringApplication實例,爲何此處又構建了一個         
    // 這是因爲這個SpringApplication實例的構建環境和SringBoot原生構建的那個不同,看一下上一行代碼就能明白
    final SpringApplication builderApplication = builder.application();
    if(builderApplication.getMainApplicationClass() == null){
        builder.main(application.getMainApplicationClass());
    }
    if (environment.getPropertySources().contains("refreshArgs")) {
        builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
    }        
    // 從springFactories文件中查找BootstrapConfiguration的配置類
    builder.sources(BootstrapImportSelectorConfiguration.class);        
    // 構建出BootstrapContext
    final ConfigurableApplicationContext context = builder.run();        
    context.setId("bootstrap");
    // 設置BootstrapContext成爲應用Context的父容器,此處分析見步驟4
    addAncestorInitializer(application, context);
    // It only has properties in it now that we don't want in the parent so remove
    // it (and it will be added back later)
    bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
    mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
    return context;
}

4.設置BootstrapContext成爲應用Context的父容器 (在SpringApplication實例中動態添加了一個初始化器,相當於給應用Context埋了個雷)

private void addAncestorInitializer(SpringApplication application, ConfigurableApplicationContext context) {
    boolean installed = false;        
    // 從spring.factories文件中獲取初始化器的配置類且類型爲AncestorInitializer
    for (ApplicationContextInitializer<?> initializer : application.getInitializers()) {
        if (initializer instanceof AncestorInitializer) {
            installed = true;
            // New parent
            ((AncestorInitializer) initializer).setParent(context);
        }
    }        
    // 默認情況下是沒有配圍置AncestorInitializer這樣的類,此處是則執行由BootstrapListener提供的內部類
    if (!installed) {            
        // 將BootstrapContext作爲父容器傳到AncestorInitializer實例中,並將其放入SpringApplication實例的初始器列表中
        application.addInitializers(new AncestorInitializer(context));
    }
}

5.初始化器AncestorInitializer被觸發,是由應用Context的處理觸發的

public void initialize(ConfigurableApplicationContext context) {  
    // 這個context是應用Context
    while (context.getParent() != null && context.getParent() != context) {
        context = (ConfigurableApplicationContext) context.getParent();
    }
    reorderSources(context.getEnvironment());            
    // 完成應用容器的父容器的設置
    new ParentContextApplicationContextInitializer(this.parent)
        .initialize(context);
}

6.ParentContextApplicationContextInitializer代碼

private static class ParentContextApplicationContextInitializer
    implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    private final ApplicationContext parent;

    ParentContextApplicationContextInitializer(ApplicationContext parent) {
        this.parent = parent;
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 設置應用Context的父容器
        applicationContext.setParent(this.parent);
    }

}

總結

  1. SpringCloud Context模塊的功能:主要是構建了一個Bootstrap容器,並讓其成爲原有的SpringBoot程序構建的容器的父容器。
  2. Bootstrap容器的作用:是爲了預先完成一些Bean的實例化工作,可以把Bootstrap容器看作是先頭部隊。
  3. Bootstrap容器的構建:是利用了SpringBoot的事件機制,當SpringBoot的初始化Environment準備好之後會發佈一個事件,這個事件的監聽器將負責完成Bootstrap容器的創建。構建時是使用SpringApplicationBuilder類來完成的。
  4. 如何讓Bootstrap容器與應用Context 建立父子關係:由於Bootstrap容器與應用Context都是關聯着同一個SpringApplication實例,Bootstrap容器自己完成初始化器的調用之後,會動態添加了一個初始化器 AncestorInitializer,相當於給應用Context埋了個雷,這個初始化器在應用容器進行初始化器調用執行時,完成父子關係的設置。

擴展

  1. spring-cloud-context源碼解讀

原文鏈接 https://www.cnblogs.com/hzhuxin/p/10496762.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章