前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Archaius] 十七、Spring的Environment是如何整合进Archaius从而实现全局共享的?

[享学Archaius] 十七、Spring的Environment是如何整合进Archaius从而实现全局共享的?

作者头像
YourBatman
发布2020-11-24 11:31:18
7860
发布2020-11-24 11:31:18
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

效率永远是公司最看重的,而不是时间的付出。

–> 返回Netflix OSS套件专栏汇总 <– 代码下载地址:https://github.com/f641385712/netflix-learning

目录

前言

上篇文章主要介绍了ArchaiusSpring Cloud的整合工程spring-cloud-starter-netflix-archaius的内容,本文将继续,会将站在实用的角度,深度分析ArchaiusAutoConfiguration该自动配置类到底做了哪些事,以及最后给出具体代码示例来体会一把Spring环境抽象EnvironmentArchaius整合的效果。

你可带着疑问阅读本文:为何Ribbon、Hystrix明明是使用Archaius管理自己的配置,而在Spring Cloud下把相应配置项写在application.properties / 配置中心里竟然都好使,何解?


正文

上篇文章介绍的spring-cloud-netflix-archaius工程里的3个类可认为均属于独立的API,和SB/SC整合并无关系,接下来便进入到整合的重点:ArchaiusAutoConfiguration自动配置类,当然喽这需要前文独立API内容的支持。


ArchaiusAutoConfiguration 自动配置类

说到Spring Boot的自动配置,相信是没有人不熟悉的。ArchaiusAutoConfiguration就是这样的一个自动配置类,它被配置在当前工程spring.factories文件里,容器启动时生效:

代码语言:javascript
复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration

初始化配置
代码语言:javascript
复制
// 说明:ConfigurationBuilder是Apache Commons Configuration1.x的核心API
// ConcurrentCompositeConfiguration是archaius1.x(0.x)的核心API
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ConcurrentCompositeConfiguration.class, ConfigurationBuilder.class })
// 优先级最高。毕竟一切都得配置先行嘛
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 
public class ArchaiusAutoConfiguration {

	private static final AtomicBoolean initialized = new AtomicBoolean(false);
	@Autowired
	private ConfigurableEnvironment env;
	@Autowired(required = false)
	private List<AbstractConfiguration> externalConfigurations = new ArrayList<>();

	private static DynamicURLConfiguration defaultURLConfig;
}
  • AtomicBoolean initialized:是否已经初始化,避免此配置被加载两次而执行两次初始化逻辑:所以此属性是个static属性哦~
  • ConfigurableEnvironment env:Spring容器的环境抽象
  • List<AbstractConfiguration> externalConfigurations:你自己扔进容器里的AbstractConfiguration配置实例,这里会被注入进来
    • 但是并没有任何的使用到的地方,因为它下面又自己用context.getBeansOfType(AbstractConfiguration.class)去容器内获取了(下文有体现),所以其实这行代码是无用的
    • 因为下面是通过static方法定义的,因此必须是自己从容器里获取(由于之前版本并非实用static,而是使用的注入的属性值,现在改为了static方式,估计代码作者忘删了)
  • DynamicURLConfiguration defaultURLConfig:static静态属性。也就是最终它会把DynamicURLConfiguration放到全局属性里去(画外音:你的config.properties文件依旧有效,但是请注意优先级)

初始化逻辑从ConfigurableEnvironmentConfiguration这个配置Bean的实例化开始:

代码语言:javascript
复制
ArchaiusAutoConfiguration:

	// 这是核心初始化流程:会把容器内的配置都放在一起,并且做一些初始化的动作
	// 把用户自定义的externalConfigurations都放进来(自己获取)
	@Bean
	public static ConfigurableEnvironmentConfiguration configurableEnvironmentConfiguration(ConfigurableEnvironment env, ApplicationContext context) {
		// externalConfigurations的获取。其实效果同@Autowired,只是这是static方法不能直接使用而已~
		Map<String, AbstractConfiguration> abstractConfigurationMap = context.getBeansOfType(AbstractConfiguration.class);
		List<AbstractConfiguration> externalConfigurations = new ArrayList<>(abstractConfigurationMap.values());

		// 组合配置
		ConfigurableEnvironmentConfiguration envConfig = new ConfigurableEnvironmentConfiguration(env);
		configureArchaius(envConfig, env, externalConfigurations);
		return envConfig;
	}


	protected static void configureArchaius(ConfigurableEnvironmentConfiguration envConfig, ConfigurableEnvironment env, List<AbstractConfiguration> externalConfigurations) {
		// initialized是静态变量:它能保证全局只会初始化一次
		if (initialized.compareAndSet(false, true)) {
			String appName = env.getProperty("spring.application.name");
			if (appName == null) {
				appName = "application";
				log.warn("No spring.application.name found, defaulting to 'application'");
			}
			// 部署上下文使用的APPId为应用名
			System.setProperty(DeploymentContext.ContextKey.appId.getKey(), appName);


			// 组合配置,我们知道`ConfigurationManager`它最终也是生成的一个组合配置
			// 这里因为要加入Spring的Environment 所以重新组装一次
			ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();

			
			// 1、自定义配置们放进来
			if (externalConfigurations != null) {
				for (AbstractConfiguration externalConfig : externalConfigurations) {
					config.addConfiguration(externalConfig);
				}
			}
			// 2、Spring的Environment环境放进来
			config.addConfiguration(envConfig, ConfigurableEnvironmentConfiguration.class.getSimpleName());

			// 3、加载`Archaius`自己的配置们放进来  所以config.properties等均还是有效的(且天生支持动态修改)
			defaultURLConfig = new DynamicURLConfiguration();
			try {
				config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
			} catch (Throwable ex) {
				log.error("Cannot create config from " + defaultURLConfig, ex);
			}

			// 默认均会条件进来
			if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
				SystemConfiguration sysConfig = new SystemConfiguration();
				config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
			}
			if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
				EnvironmentConfiguration environmentConfiguration = new EnvironmentConfiguration();
				config.addConfiguration(environmentConfiguration, ENV_CONFIG_NAME);
			}
			ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
			config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
			config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));

			// 简单的说就是调用:ConfigurationManager.install(config);
			// 把该组合配置放进全局配置里
			addArchaiusConfiguration(config);
		} else { // 全局配置已经安装过 无需再安装
			log.warn("Netflix ConfigurationManager has already been installed, unable to re-install");
		}
	}

此配置初始化步骤看似复杂,但其实总结起来蛮简单的:将Spring环境、用户自定义的配置externalConfigurationsArchaius自己管理的配置组合起来,放进全局配置里。步骤可总结如下:

  1. 部署上下文DeploymentContext应用名称默认使用Spring Boot的应用名称:spring.application.name(若没指定默认名为application
  2. 使用组合配置类ConcurrentCompositeConfiguration作为最终的全局配置。最先把用户自定义的放进去(优先级最高)
  3. 然后是Spring的ConfigurableEnvironmentConfiguration环境抽象(优先级第二)
  4. 然后是Archaius管理的DynamicURLConfiguration(优先级第三,如config.properties里的配置)
  5. 最后若没显示禁用的话,会把SystemConfiguration、EnvironmentConfiguration放进去

不妥之处

初始化流程结束后,完成了全局Configuration配置的内容填充,并且最后把ConfigurableEnvironmentConfiguration这个Bean放进了容器里,其实这是不妥的,原因如下:

  1. ConfigurableEnvironmentConfiguration仅是Environment的一个包装器,因此它有且仅包含Spring环境内的配置,是不完整的
  2. 若配置属性是Archaius专属管理的,如config.properties里的或者ConfigurationManager.getConfigInstance().addProperty("name", "YourBatman")添加进来的,通过它都是获取不到的。

因此指出把它放进容器内的不妥之处(或者说是使用时候需要特别注意的地方),最好不要直接使用它是最安全的。


代码示例

config.properties内容:

代码语言:javascript
复制
my.name = YourBatman
my.age = 100

application.yaml内容:

代码语言:javascript
复制
my:
  age: 18

启动Spring Boot容器测试:

代码语言:javascript
复制
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

        // 读取配置
        AbstractConfiguration config = ConfigurationManager.getConfigInstance();
        System.out.println(config.getString("my.name"));
        System.out.println(config.getString("my.age"));

        DynamicStringProperty name = DynamicPropertyFactory.getInstance().getStringProperty("my.name", "defualtName");
        DynamicIntProperty age = DynamicPropertyFactory.getInstance().getIntProperty("my.age", 0);
        System.out.println(name);
        System.out.println(age);

        context.close();
    }

}

控制台输出:

代码语言:javascript
复制
YourBatman
18
DynamicProperty: {name=my.name, current value=YourBatman}
DynamicProperty: {name=my.age, current value=18}

可以看到application.yaml的优先级是更高的,也就是Spring的env比Archaius管理的配置是具有更高优先级的,从全局配置里也可看出,如下截图:

在这里插入图片描述
在这里插入图片描述

现在应该能够解答你的疑惑:为何Ribbon、Hystrix明明是使用Archaius管理着配置的,为何你把配置写在application.properties/配置中心里依旧好使了吧(并且还支持动态刷新哦),其核心便是“替换”了全局配置。


全局配置如何感知到Spring环境属性的变更

在使用开发中,我们的配置大都写在application.properties/yaml里,或者在配置中心里(而并不会放在conifg.properties里),总之最终都会被放进Spring的Environment里,那么问题就来了:全局配置如何感知到Spring环境属性的变更,从而保持同步性呢

这时候Spring Cloud就出马了,利用org.springframework.cloud.context.environment.EnvironmentChangeEvent这个事件就能很好的完成这项工作,这也就是为何工程名是spring-cloud-xxx的唯一原因吧:

代码语言:javascript
复制
ArchaiusAutoConfiguration:

	// 它是个ApplicationListener,监听EnvironmentChangeEvent事件
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnProperty(value = "archaius.propagate.environmentChangedEvent", matchIfMissing = true)
	@ConditionalOnClass(EnvironmentChangeEvent.class)
	protected static class PropagateEventsConfiguration implements ApplicationListener<EnvironmentChangeEvent> {

		@Autowired
		private Environment env;

		@Override
		public void onApplicationEvent(EnvironmentChangeEvent event) {
			// 拿到全局配置
			AbstractConfiguration manager = ConfigurationManager.getConfigInstance();

			// 从事件里拿到所有改变了的Key们,一个一个的处理
			for (String key : event.getKeys()) {
				// 拿到注册在Configuration上的所有事件门,一个一个的触发他们的configurationChanged方法
				// 事件类型是:AbstractConfiguration.EVENT_SET_PROPERTY;
				for (ConfigurationListener listener : manager.getConfigurationListeners()) {
					Object source = event.getSource();
					// TODO: Handle add vs set vs delete?
					int type = AbstractConfiguration.EVENT_SET_PROPERTY;
					// 改变后的value从env里获取(可能为null哦~)
					// 当然一般建议动态修改,而非删除,请务必注意喽
					String value = this.env.getProperty(key);
					boolean beforeUpdate = false;
					listener.configurationChanged(new ConfigurationEvent(source, type, key, value, beforeUpdate));
				}
			}
		}
	}

逻辑很简单:向容器内放一个ApplicationListener监听器监听着EnvironmentChangeEvent事件,对所有发生变更的key们,逐个触发其AbstractConfiguration.EVENT_SET_PROPERTY事件从而借助Archaius自己的同步机制更新全局配置属性。

你可以通过archaius.propagate.environmentChangedEvent=false来显示的关闭这个行为,但很显然一般你并不需要这么做。


关于Archaius2.x

其实Archaius1.x(或者说0.x)现在基本处于一个停更(只维护)状态,一般来说软件到这个状态,生命的尽头就快到了,更何况整个Netflix大都均已经进入维护阶段了呢。做为这么优秀的一个配置管理库,实际上Archaius是在继续(独立)发展的,那就是它的2.x版本:

代码语言:javascript
复制
<dependency>
    <groupId>com.netflix.archaius</groupId>
    <artifactId>archaius2-core</artifactId>
    <version>2.3.16</version>
</dependency>

它采用了全新的API设计(不向下兼容),并且采用API + 实现分离的方式,并不强依赖于Commons Configuration来实现,可扩展性更强了。Archaius2.x虽然优点众多,但是,但是,但是:由于不管是现在的Netflix OSS,还是Spring Cloud Netflix xxx(哪怕到了最新版本)依赖的均还是Archaius1.x版本(0.7.7版本),这也是为何本系列只讲1.x分支的原因,毕竟还是要以实用为主。

不过话说回来:我对Archaius2.x的印象还是不错的,如果你仅仅是需要一个配置库作为你的基础配置管理支撑,选择Archaius2.x是个非常好的选择,在功能性、可扩展性上它都大大优于1.x。但是现在我们大都处于Spring体系的温室里,因此请各位自行衡量投入产出比~


总结

关于Netflix ArchaiusSpring Cloud的整合,以及整个Archaius系列就全部介绍完了。配置对于一个程序的重要程度不言而喻,多么过分的强调都不为过,这也是为何我觉得现在称呼某些架构师为配置工程师是更合适的,因此本系列花重笔墨专题介绍了Archaius以及Apache Commons Configuration的使用和原理,它作为基础中的基础,在Netflix其它专题系列如Hystrix、Ribbon等中还会经常见面的~

最后再强调一点:即便到了Spring Boot2.2.x这么高的版本,它依赖的依旧还都是Archaius 1.x以及Commons Configuration1.x版本。所以很多时候切忌不要为了追新而追新,而是要围绕其背后的生态来学习才最具价值。


本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 前言
  • 正文
    • ArchaiusAutoConfiguration 自动配置类
      • 初始化配置
      • 代码示例
    • 全局配置如何感知到Spring环境属性的变更
      • 关于Archaius2.x
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档