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

  • 相关阅读:
    Yii2 在模块modules间跳转时,url自动加模块名
    PHP 变量的间接引用(将某一字符串转化为变量)
    windows鼠标悬停任务栏 延迟时间 修改
    dede 常用标签和调用方法汇总
    dedecms ---m站功能基础详解
    apache 2.2 和2.4 目录权限访问设置的区别
    apache httpd.conf 配置局域网访问
    ajax php 点击加载更多
    dede调用当前栏目名 、dede sql
    dede 添加 栏目缩略图
  • 原文地址:https://www.cnblogs.com/question-sky/p/10245384.html
Copyright © 2011-2022 走看看