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额外的特色。

相关文章
相关标签/搜索