学技术不仅仅是要学技术本身,还有其思想,更重要是它的发展历史脉络。因为熟悉了这些,你便会从哲学的角度去思考问题,从而对其它技术也能触类旁通。 代码下载地址:https://github.com/f641385712/netflix-learning
截止到上篇文章,其实关于Netflix Archaius
的内容都已经讲述完了,理论上你现在应该可以没有障碍的使用它了。
本来本文我是没有打算去写的,因为掌握了核心后,去集成任何技术都是不算太难的一件事。但是,但是,但是,奈何Spring Boot/Cloud
如此之火,现在一门技术如果不谈和它的整合,都不好意思说自己出道了。
基于此,本文就接着介绍下Netflix Archaius
它和Spring Cloud的整合工程:spring-cloud-starter-netflix-archaius
。
在阅读接下来内容,请务必确保你已经了解了Netflix Archaius
的核心知识,以及Spring Cloud
的基础支持:特别是Spring Cloud Context以及它的Commons抽象~。
说明:关于
Netflix Archaius
核心知识,你可以从 上篇文章(也就是十四、十三…)去了解。
首先需要明确:整合方面是Spring Cloud官方去整合Netflix Archaius
,所以它属于一个官方支持的项目。那么一切就从它的官方工程说起:
GAV如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-archaius</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
从1.4.0.RELEASE
(2017.11)至今,当前最新版本为2.2.1.RELEASE
,该Jar携带如下内容:
我本人有个疑问:为毛它会把spring-cloud-netflix-ribbon
带进来,却又其实并没有任何地方使用到它,毕竟archaius
属于更为底层的基础不可能使用上层API。
反倒而其实spring-cloud-netflix-ribbon
它自己是携带spring-cloud-netflix-archaius
的,所以这个starter的依赖管理得着实让我费解,若有可解释得通的朋友,欢迎你留言相告~
另外顺便还解答一个小疑问:工程名为何不叫spring-boot-starter-netflix-archaius
,而叫spring-cloud-xxx
呢?我找到了此唯一原因:它使用到了org.springframework.cloud.context.environment.EnvironmentChangeEvent
这个Spring Cloud
的标准事件,从而成功和Spring Cloud整合,所以它必须构建在Spring Cloud下,而非Spring Boot。
他俩的GAV如下,非常相似有木有。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-archaius</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-archaius</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
这个问题出自于不止一个小伙伴的提问,因此这里我也顺道解释一波。从表象上看,我相信你借助IDEA就能看出来:spring-cloud-starter-netflix-archaius
管理着spring-cloud-netflix-archaius
以及其它相关依赖。
为了授之以渔,此处我额外用两点帮你区分这一类问题而不止是这一个问题:
Spring Boot
官方推荐你自定义的stater至少有两个模块 autoconfigure
模块:包含自动配置的代码starter
模块:提供对autoconfigure模块的依赖,以及一些其它的依赖starter
模块一般无代码,是个空jar。它唯一的目的是提供这个库所必须的依赖(就是管理依赖用的)官方自己的starter均遵循此规律来实现,譬如:
spring-boot-starter
和spring-boot
spring-boot-starter-actuator
和spring-boot-actuator
spring-boot-starter-aop
和spring-aop + aspectjweaver...
所以,spring-cloud-starter-netflix-archaius
它包含有spring-cloud-netflix-archaius
以及其它依赖,作为starter它只是帮你管理着那些必须依赖而已,而实际干事的是spring-cloud-netflix-archaius
模块内的Java文件。
它依赖于archaius-core 0.7.6
实现的动态配置管理,其它依赖项均交给spring-cloud-starter-netflix-archaius
管理着。
该Jar的内容并不多,有且仅有4个类:
它是对Spring环境抽象org.springframework.core.env.ConfigurableEnvironment
的一个包装,适配为org.apache.commons.configuration.AbstractConfiguration
,从而便可和Archaius
无缝整合。
public class ConfigurableEnvironmentConfiguration extends AbstractConfiguration {
// 管理着Spring的所有属性们
private final ConfigurableEnvironment environment;
public ConfigurableEnvironmentConfiguration(ConfigurableEnvironment environment) {
this.environment = environment;
}
...
@Override
public boolean isEmpty() {
return !getKeys().hasNext();
}
@Override
public boolean containsKey(String key) {
return this.environment.containsProperty(key);
}
@Override
public Object getProperty(String key) {
return this.environment.getProperty(key);
}
// 拿到所有的属性源PropertySource出来,注意这里需要处理CompositePropertySource这种哟
@Override
public Iterator<String> getKeys() {
List<String> result = new ArrayList<>();
for (Map.Entry<String, PropertySource<?>> entry : getPropertySources().entrySet()) {
PropertySource<?> source = entry.getValue();
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
for (String name : enumerable.getPropertyNames()) {
result.add(name);
}
}
}
return result.iterator();
}
...
}
该类的唯一作用:把Spring的环境抽象ConfigurableEnvironment
适配为一个Configuration
,从而可以加入到全局配置里面去。
说明:很多小伙伴看到它最终会被
ArchaiusAutoConfiguration
配置为一个Bean放到容器内,其实那是没有必要的,问下会解释缘由。
工具类。代理了ApplicationContext#getBean()
等方法~
public final class ArchaiusDelegatingProxyUtils {
// Application作为属性的key名称
public static String APPLICATION_CONTEXT = ApplicationContext.class.getName();
// 简单的说:就是去ApplicationContext里面去getBean
// 前提是:ApplicationContext实例必须在全局的Configuration里面了~
public static <T> T getNamedInstance(Class<T> type, String name) {
ApplicationContext context = (ApplicationContext) ConfigurationManager.getConfigInstance().getProperty(APPLICATION_CONTEXT);
return context != null && context.containsBean(name) ? context.getBean(name, type) : null;
}
// name = prefix + type.getSimpleName();
public static <T> T getInstanceWithPrefix(Class<T> type, String prefix) {
String name = prefix + type.getSimpleName();
return getNamedInstance(type, name);
}
// 调用此方法:可以把ApplicationContext实例,放进Configuration里面
public static void addApplicationContext(ConfigurableApplicationContext context) {
AbstractConfiguration config = ConfigurationManager.getConfigInstance();
config.clearProperty(APPLICATION_CONTEXT);
config.setProperty(APPLICATION_CONTEXT, context);
}
}
注意:以上三个工具方法,默认情况下没有被任何地方用到,所以当你需要自行扩展的时候,可以使用。
用于访问Archaius
的配置的端点:有且仅有一个读方法,并无写方法
@Endpoint(id = "archaius")
public class ArchaiusEndpoint {
// 只有一个读方法而已
@ReadOperation
public Map<String, Object> invoke() {
Map<String, Object> map = new LinkedHashMap<>();
AbstractConfiguration config = ConfigurationManager.getConfigInstance();
if (config instanceof ConcurrentCompositeConfiguration) {
ConcurrentCompositeConfiguration composite = (ConcurrentCompositeConfiguration) config;
for (Configuration item : composite.getConfigurations()) {
append(map, item);
}
} else {
append(map, config);
}
return map;
}
// 核心在append方法,往Map里放值
// 需要注意的是:前面三种属性源都是不会添加进去的哦,就连ConfigurableEnvironmentConfiguration都不给你访问
private void append(Map<String, Object> map, Configuration config) {
if (config instanceof ConfigurableEnvironmentConfiguration)
return;
if (config instanceof SystemConfiguration)
return;
if (config instanceof EnvironmentConfiguration)
return;
for (Iterator<String> iter = config.getKeys(); iter.hasNext();) {
String key = iter.next();
map.put(key, config.getProperty(key));
}
}
}
访问示例:http://localhost:8080/actuator/archaius
,默认情况下返回值是{}
(因为你并没有手动给它设过值嘛),下面给给添加几个值:
public static void main(String[] args) {
AbstractConfiguration config = ConfigurationManager.getConfigInstance();
config.addProperty("name", "YourBatman");
config.addProperty("age", 18);
ConfigurableApplicationContext context = SpringApplication.run(CloudFeignApplication.class, args);
// 这算是一个Bug,当把Context放进去后,如果访问`archaius`端点,会报错:序列化异常,所以此处展示注释掉喽
// ArchaiusDelegatingProxyUtils.addApplicationContext(context);
}
运行程序,再次访问http://localhost:8080/actuator/archaius
,控制台输出:
{"name":"YourBatman","age":18}
完美~
说明:此端点会有序列化的过程,所以若你存在不能被序列化的属性,此端点就会抛错哦。比如文上说的ApplicationContext就不能被正常序列化~
以上3个类均是独立的API,有且仅有本来和Spring Boot/Cloud
打了交道。它被配置在当前工程的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);
// Spring容器的环境
@Autowired
private ConfigurableEnvironment env;
// 由此可知:你若要扩展属性Configuration,直接往容器里扔一个Bean即可~~~非常方便
@Autowired(required = false)
private List<AbstractConfiguration> externalConfigurations = new ArrayList<>();
// 用于加载Archaius的那些默认行为,比如类路径下的config.properties
private static DynamicURLConfiguration defaultURLConfig;
// 这是核心初始化流程:会把容器内的配置都放在一起,并且做一些初始化的动作
// 把用户自定义的externalConfigurations都放进来
@Bean
public static ConfigurableEnvironmentConfiguration configurableEnvironmentConfiguration(ConfigurableEnvironment env, ApplicationContext context) {
ConfigurableEnvironmentConfiguration envConfig = new ConfigurableEnvironmentConfiguration(env);
configureArchaius(envConfig, env, externalConfigurations);
return envConfig;
}
protected static void configureArchaius(...) {
// 保证只初始化一次
if (initialized.compareAndSet(false, true)) {
// 把应用名称,appName放进系统属性(全局配置)
String appName = env.getProperty("spring.application.name");
if (appName == null) {
appName = "application";
log.warn("No spring.application.name found, defaulting to 'application'");
}
System.setProperty(DeploymentContext.ContextKey.appId.getKey(), appName);
// 请注意:它是一个组合属性,将会组合下面的一些配置们
ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
... // 把用户额外配置的属性都放进来
config.addConfiguration(externalConfig);
// 把Spring Env属性放进来
config.addConfiguration(envConfig, ConfigurableEnvironmentConfiguration.class.getSimpleName());
// 把形如config.properties这种配置加进来(默认是empty哦)
defaultURLConfig = new DynamicURLConfiguration();
config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
// 如果系统属性、系统环境没有被禁止
// 那就把new SystemConfiguration()、 new EnvironmentConfiguration()加进来
if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG))
if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG))
// 把组合好的配置,安装到全局的Configuration里面去
// 需要注意的是:这里install是指定了外部config的
// 所以即使你内部`ConfigurationManager#createDefaultConfigInstance`过,最终也会被这个给替换掉
// 但其里面的内容都会被copy过来,不会丢失哦~~~~
// 所以这个install是专门用于设置外部config进来的~~~~
... ConfigurationManager.install(config);
// addArchaiusConfiguration(config);
}
}
...
@Bean
@ConditionalOnEnabledEndpoint
protected ArchaiusEndpoint archaiusEndpoint() {
return new ArchaiusEndpoint();
}
...
}
以上初始化步骤看似复杂,但其实只做了一件事:将Spring环境配置、用户自定义的配置externalConfigurations
,以及你已经放进去的一些属性们全部放进全局Configuration里。
它的初始化步骤可总结如下:
appName
放进去(应用名由spring.application.name
来定,否则默认值是application)Configuration
Bean放进去(因为它最先放进去,所以优先级最高)ConfigurableEnvironmentConfiguration
放进去(优先级第二)Archaius
自己的DynamicURLConfiguration
放进去SystemConfiguration、EnvironmentConfiguration
放进去(若没禁止的话)最终,全局Configuration
属性的值的截图参考如下:
初始化结束后,不仅仅完成了全局Configuration
的初始化,并且然后还把ConfigurableEnvironmentConfiguration
放进了容器(其实我觉得它是完全没必要放进容器里的,因为它里面的属性并不全,我们也不会直接用它:只有Spring容器内的,向你通过ConfigurationManager.getConfigInstance().addProperty("name", "YourBatman")
这种方式放进去的话它是获取不到的哦)。
在使用开发中,我们的配置大都写在application.properties/yaml
里,或者在配置中心里(而并不会放在conifg.properties
里),总之最终都会被放进Spring 的Enviroment
里,那么问题就来了:全局配置如何感知到Spring环境属性的变更,从而保持同步性呢?
这时候Spring Cloud
就出马了,利用org.springframework.cloud.context.environment.EnvironmentChangeEvent
这个事件就能很好的完成这个工作:
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));
}
}
}
}
逻辑很简单:对所有发生变更的key们,逐个触发其AbstractConfiguration.EVENT_SET_PROPERTY
事件从而同步更新全局配置属性。
另外,你可以通过archaius.propagate.environmentChangedEvent=false
来显示的关闭这个行为,但很显然一般你并不需要这么做。
使用示例在Spring Cloud
配置中心篇章里会回溯到此,请出门参阅。
其实Archaius1.x
(或者说0.x)现在基本处于一个停更(只维护)状态,一般来说软件到这种时候,生命的尽头就快到了。
说明:
Archaius1.x
的最新版本是0.7.7(和0.7.6差不多)
而实际上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
虽然优点众多,但是,但是,但是:由于不管是现在的Hystrix
,还是Spring Cloud Netflix xxx
(哪怕到了最新版本)依赖的均是Archaius1.x
版本(0.7.7版本),所以本系列只讲1.x,而不讲2.x。
还是一样的,万变不离其宗,有兴趣的小伙伴可自行研究Archaius2.x
如何使用?
关于Netflix Archaius和Spring Cloud的集成部分就说到这了,至此全部关于Archaius
的内容就介绍完了,它作为基础中的基础,后面章节将会使用到它,所以还会频繁见面哦~
另外提示一点:你可以看到,即便到了Spring Boot2.2.x
这么高的版本,它依赖的依旧还都是Archaius 1.x
版本以及Commons Configuration1.x版本。所以说,即使Commons Configuration2.x
已成为主流被推荐使用,但是1.x依旧有很高的学习价值的,请勿忽视它哦。