springcloud情操陶冶-bootstrapContext(一)

基於前文對springcloud的引導,本文則從源碼角度查閱下cloud的context板塊的運行邏輯html

前言

springcloud是基於springboot開發的,因此讀者在閱讀此文前最好已經瞭解了springboot的工做原理。本文將不闡述springboot的工做邏輯java

Cloud Context

springboot cloud context在官方的文檔中在第一點被說起,是用戶ApplicationContext的父級上下文,筆者稱呼爲BootstrapContext。根據springboot的加載機制,不少第三方以及重要的Configuration配置均是保存在了spring.factories文件中。
筆者翻閱了spring-cloud-context模塊下的對應文件,見以下web

# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

涉及的主要分三類,筆者優先分析監聽器,其通常擁有更高的優先級並跟其餘兩塊有必定的關聯性。
除了日誌監聽器筆者不太關注,其他兩個分步驟來分析spring

RestartListener

重啓監聽器,應該是用於刷新上下文的,直接查看下其複寫的方法bootstrap

@Override
    public void onApplicationEvent(ApplicationEvent input) {
        // 應用預備事件,先緩存context
        if (input instanceof ApplicationPreparedEvent) {
            this.event = (ApplicationPreparedEvent) input;
            if (this.context == null) {
                this.context = this.event.getApplicationContext();
            }
        }
        // 上下文刷新結束事件,從新傳播ApplicationPreparedEvent事件
        else if (input instanceof ContextRefreshedEvent) {
            if (this.context != null && input.getSource().equals(this.context)
                    && this.event != null) {
                this.context.publishEvent(this.event);
            }
        }
        else {
            // 上下文關閉事件傳播至此,則開始清空所擁有的對象
            if (this.context != null && input.getSource().equals(this.context)) {
                this.context = null;
                this.event = null;
            }
        }
    }

上述的刷新事件通過查閱,與org.springframework.cloud.context.restart.RestartEndpoint類有關,這個就後文再分析好了緩存

BootstrapApplicationListener

按照順序分析此監聽器springboot


1.優先看下其類結構app

public class BootstrapApplicationListener
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
        }

此監視器是用於響應ApplicationEnvironmentPreparedEvent應用環境變量預初始化事件,代表BootstrapContext的加載時機在用戶上下文以前,且其加載順序比ConfigFileApplicationListener監聽器超前,這點稍微強調下。ide


2.接下來分析下其複寫的方法onApplicationEvent(ApplicationEnvironmentPreparedEvent event)函數

@Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        // 獲取環境變量對象
        ConfigurableEnvironment environment = event.getEnvironment();
        // 讀取spring.cloud.bootstrap.enabled環境屬性,默認爲true。可經過系統變量設置
        if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
                true)) {
            return;
        }
        // don't listen to events in a bootstrap context
        if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            return;
        }
        // 尋找當前環境是否已存在BootstrapContext
        ConfigurableApplicationContext context = null;
        String configName = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
        for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
                .getInitializers()) {
            if (initializer instanceof ParentContextApplicationContextInitializer) {
                context = findBootstrapContext(
                        (ParentContextApplicationContextInitializer) initializer,
                        configName);
            }
        }
        // 若是尚未被建立,則開始建立
        if (context == null) {
            context = bootstrapServiceContext(environment, event.getSpringApplication(),
                    configName);
            // 註冊註銷監聽器
            event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
        }

        // 加載BoostrapContext上的ApplicationContextInitializers到用戶Context上
        apply(context, event.getSpringApplication(), environment);
    }

邏輯很簡單,筆者梳理下

  • spring.cloud.bootstrap.enabled 用於配置是否啓用BootstrapContext,默認爲true。可採起系統變量設定
  • spring.cloud.bootstrap.name 用於加載bootstrap對應配置文件的別名,默認爲bootstrap
  • BootstrapContext上的beanType爲ApplicationContextInitializer類型的bean對象集合會被註冊至用戶的Context上

3.重點看下BootstrapContext的建立過程,源碼比較長,但筆者認爲仍是頗有必要拿出來

/**
     *
     *  create bootstrap context
     *
     * @param environment   全局Environment
     * @param application   用戶對應的Application
     * @param configName    bootstrapContext對應配置文件的加載名,默認爲bootstrap
     * @return  bootstrapContext
     */
    private ConfigurableApplicationContext bootstrapServiceContext(
            ConfigurableEnvironment environment, final SpringApplication application,
            String configName) {
        // create empty environment
        StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
        MutablePropertySources bootstrapProperties = bootstrapEnvironment
                .getPropertySources();
        for (PropertySource<?> source : bootstrapProperties) {
            bootstrapProperties.remove(source.getName());
        }
        // 讀取spring.cloud.bootstrap.location屬性,通常經過系統變量設置,默認爲空
        String configLocation = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
        Map<String, Object> bootstrapMap = new HashMap<>();
        bootstrapMap.put("spring.config.name", configName);
        bootstrapMap.put("spring.main.web-application-type", "none");
        // 加載bootstrapContext配置文件的路徑,與spring.config.name搭配使用
        if (StringUtils.hasText(configLocation)) {
            bootstrapMap.put("spring.config.location", configLocation);
        }
        bootstrapProperties.addFirst(
                new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
        for (PropertySource<?> source : environment.getPropertySources()) {
            if (source instanceof StubPropertySource) {
                continue;
            }
            bootstrapProperties.addLast(source);
        }
        // use SpringApplicationBuilder to create bootstrapContext
        SpringApplicationBuilder builder = new SpringApplicationBuilder()
                // 此處activeProfiles是經過系統變量設置的,此處稍微備註下
                .profiles(environment.getActiveProfiles())
                .bannerMode(Mode.OFF)
                // 應用bootstrap自己的環境變量
                .environment(bootstrapEnvironment)
                // Don't use the default properties in this builder
                .registerShutdownHook(false).logStartupInfo(false)
                .web(WebApplicationType.NONE);
        final SpringApplication builderApplication = builder.application();
        // 配置入口函數類
        if (builderApplication.getMainApplicationClass() == null) {
            builder.main(application.getMainApplicationClass());
        }
        
        if (environment.getPropertySources().contains("refreshArgs")) {
            builderApplication
                    .setListeners(filterListeners(builderApplication.getListeners()));
        }
        // 增長入口類BootstrapImportSelectorConfiguration
        builder.sources(BootstrapImportSelectorConfiguration.class);
        // create
        final ConfigurableApplicationContext context = builder.run();
        // 設置bootstrapContext的別名爲bootstrap
        context.setId("bootstrap");
        // 配置bootstrapContext爲用戶Context的父類
        addAncestorInitializer(application, context);
        // 合併defaultProperties對應的變量至childEnvironment
        bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
        mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
        return context;
    }

此處也對上述的代碼做下簡單的小結

  • spring.cloud.bootstrap.location變量用於配置bootstrapContext配置文件的加載路徑,可用System設置,默認則採起默認的文件搜尋路徑;與spring.cloud.bootstrap.name搭配使用
  • bootstrapContext對應的activeProfiles可採用spring.active.profiles系統變量設置,注意是System變量。固然也能夠經過bootstrap.properties/bootstrap.yml配置文件設置
  • bootstrapContext的重要入口類爲BootstrapImportSelectorConfiguration,此也是下文的分析重點
  • bootstrapContext的contextId爲bootstrap。即便配置了spring.application.name屬性也會被設置爲前者,且其會被設置爲用戶Context的父類
  • bootstrap.(yml|properties)上的配置會被合併至用戶級別的Environment中的defaultProperties集合中,且其相同的KEY會被丟棄,不一樣KEY會被保留。即其有最低的屬性優先級

經過上述的代碼都可以得知,bootstrapContext也是經過springboot常見的SpringApplication方式來建立的,但其確定有特別的地方。
特別之處就在BootstrapImportSelectorConfiguration類,其也與上述spring.factories文件中org.springframework.cloud.bootstrap.BootstrapConfiguration的Key有直接的關係,咱們下文重點分析

後記

因爲繼續分析會致使篇幅過長,遂片斷式,這樣有助於深刻理解以及後期回顧。下文便會主要分析下bootstrapContext額外的特色。

相關文章
相關標籤/搜索