效率永远是公司最看重的,而不是时间的付出。
–> 返回Netflix OSS套件专栏汇总 <– 代码下载地址:https://github.com/f641385712/netflix-learning
上篇文章主要介绍了Archaius
和Spring Cloud
的整合工程spring-cloud-starter-netflix-archaius
的内容,本文将继续,会将站在实用的角度,深度分析ArchaiusAutoConfiguration
该自动配置类到底做了哪些事,以及最后给出具体代码示例来体会一把Spring环境抽象Environment
和Archaius
整合的效果。
你可带着疑问阅读本文:为何Ribbon、Hystrix明明是使用Archaius
管理自己的配置,而在Spring Cloud下把相应配置项写在application.properties / 配置中心
里竟然都好使,何解?
上篇文章介绍的spring-cloud-netflix-archaius
工程里的3个类可认为均属于独立的API,和SB/SC整合并无关系,接下来便进入到整合的重点:ArchaiusAutoConfiguration
自动配置类,当然喽这需要前文独立API内容的支持。
说到Spring Boot的自动配置,相信是没有人不熟悉的。ArchaiusAutoConfiguration
就是这样的一个自动配置类,它被配置在当前工程的spring.factories
文件里,容器启动时生效:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration
// 说明: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)
去容器内获取了(下文有体现),所以其实这行代码是无用的DynamicURLConfiguration defaultURLConfig
:static静态属性。也就是最终它会把DynamicURLConfiguration
放到全局属性里去(画外音:你的config.properties
文件依旧有效,但是请注意优先级)初始化逻辑从ConfigurableEnvironmentConfiguration
这个配置Bean的实例化开始:
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环境、用户自定义的配置externalConfigurations
、Archaius
自己管理的配置组合起来,放进全局配置里。步骤可总结如下:
spring.application.name
(若没指定默认名为application
)ConcurrentCompositeConfiguration
作为最终的全局配置。最先把用户自定义的放进去(优先级最高)ConfigurableEnvironmentConfiguration
环境抽象(优先级第二)Archaius
管理的DynamicURLConfiguration
(优先级第三,如config.properties
里的配置)SystemConfiguration、EnvironmentConfiguration
放进去初始化流程结束后,完成了全局Configuration
配置的内容填充,并且最后把ConfigurableEnvironmentConfiguration
这个Bean放进了容器里,其实这是不妥的,原因如下:
ConfigurableEnvironmentConfiguration
仅是Environment的一个包装器,因此它有且仅包含Spring环境内的配置,是不完整的Archaius
专属管理的,如config.properties
里的或者ConfigurationManager.getConfigInstance().addProperty("name", "YourBatman")
添加进来的,通过它都是获取不到的。因此指出把它放进容器内的不妥之处(或者说是使用时候需要特别注意的地方),最好不要直接使用它是最安全的。
config.properties
内容:
my.name = YourBatman
my.age = 100
application.yaml
内容:
my:
age: 18
启动Spring Boot容器测试:
@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();
}
}
控制台输出:
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/配置中心
里依旧好使了吧(并且还支持动态刷新哦),其核心便是“替换”了全局配置。
在使用开发中,我们的配置大都写在application.properties/yaml
里,或者在配置中心里(而并不会放在conifg.properties
里),总之最终都会被放进Spring的Environment
里,那么问题就来了:全局配置如何感知到Spring环境属性的变更,从而保持同步性呢?
这时候Spring Cloud
就出马了,利用org.springframework.cloud.context.environment.EnvironmentChangeEvent
这个事件就能很好的完成这项工作,这也就是为何工程名是spring-cloud-xxx的唯一原因吧:
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
来显示的关闭这个行为,但很显然一般你并不需要这么做。
其实Archaius1.x
(或者说0.x)现在基本处于一个停更(只维护)状态,一般来说软件到这个状态,生命的尽头就快到了,更何况整个Netflix大都均已经进入维护阶段了呢。做为这么优秀的一个配置管理库,实际上Archaius
是在继续(独立)发展的,那就是它的2.x版本:
<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 Archaius
和Spring Cloud
的整合,以及整个Archaius
系列就全部介绍完了。配置对于一个程序的重要程度不言而喻,多么过分的强调都不为过,这也是为何我觉得现在称呼某些架构师为配置工程师是更合适的,因此本系列花重笔墨专题介绍了Archaius
以及Apache Commons Configuration
的使用和原理,它作为基础中的基础,在Netflix其它专题系列如Hystrix、Ribbon等中还会经常见面的~
最后再强调一点:即便到了Spring Boot2.2.x
这么高的版本,它依赖的依旧还都是Archaius 1.x
以及Commons Configuration1.x
版本。所以很多时候切忌不要为了追新而追新,而是要围绕其背后的生态来学习才最具价值。