zoukankan      html  css  js  c++  java
  • springcloud情操陶冶-bootstrapContext(一)

    基于前文对springcloud的引导,本文则从源码角度查阅下cloud的context板块的运行逻辑

    前言

    springcloud是基于springboot开发的,所以读者在阅读此文前最好已经了解了springboot的工作原理。本文将不阐述springboot的工作逻辑

    Cloud Context

    springboot cloud context在官方的文档中在第一点被提及,是用户ApplicationContext的父级上下文,笔者称呼为BootstrapContext。根据springboot的加载机制,很多第三方以及重要的Configuration配置均是保存在了spring.factories文件中。
    笔者翻阅了spring-cloud-context模块下的对应文件,见如下

    # 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
    

    涉及的主要分三类,笔者优先分析监听器,其一般拥有更高的优先级并跟其他两块有一定的关联性。
    除了日志监听器笔者不太关注,其余两个分步骤来分析

    RestartListener

    重启监听器,应该是用于刷新上下文的,直接查看下其复写的方法

    	@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

    按照顺序分析此监听器


    1.优先看下其类结构

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

    此监视器是用于响应ApplicationEnvironmentPreparedEvent应用环境变量预初始化事件,表明BootstrapContext的加载时机在用户上下文之前,且其加载顺序比ConfigFileApplicationListener监听器超前,这点稍微强调下。


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

  • 相关阅读:
    CNN comprehension
    Gradient Descent
    Various Optimization Algorithms For Training Neural Network
    gerrit workflow
    jenkins job配置脚本化
    Jenkins pipeline jobs隐式传参
    make words counter for image with the help of paddlehub model
    make words counter for image with the help of paddlehub model
    git push and gerrit code review
    image similarity
  • 原文地址:https://www.cnblogs.com/question-sky/p/10245384.html
Copyright © 2011-2022 走看看