踩坑Apollo配置namespace加载顺序优先级问题,更具体点应该表述为【踩坑Spring框架中针对多配置属性源取值的逻辑】。
SprintBoot
项目中使用了携程开源的Apollo
组件完成分布式配置功能。其中有一个common
包负责公用一些基础配置,工程特有的配置通过namespace
进行区分。
配置项长这样:
apollo.bootstrap.enabled = true
apollo.bootstrap.namespaces = common,application
问题表现:
在管理界面修改namespace为application
的某一个项时,发现配置不生效。
去找了下中文文档,针对Java客户端使用,集成到Spring
的描述中针对此项有具体解释:注入多个namespace,并且指定顺序。
此处规则很明确:在前面的namespace优先级最高,谁在前面哪个值生效。
源码解析#
Apollo中通过ApolloApplicationContextInitializer这个类implements了Spring框架中的ApplicationContextInitializer类,内部实现了initialize(ConfigurableApplicationContext context)
方法。这里负责把各命名空间中的配置项加载到java对象(PropertySources)中,交给spring管理起来。
我们详细看看initialize(ConfigurableEnvironment environment)
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/**
* 环境变量初始化动作
*
* @param environment
*/
protected void initialize(ConfigurableEnvironment environment) {
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
// 这里判断apollo启动配置中是否已注入 ApolloPropertySources,fail-fast逻辑
return;
}
// 从 apollo.bootstrap.namespaces 中取配置的命名空间,默认使用 application。此处其实已经表明下游自定义的项应该配在application空间中,但是要保证namespace顺序application在前面。
String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
logger.debug("Apollo bootstrap namespaces: {}", namespaces);
List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
for (String namespace : namespaceList) {
// 每个命名空间会对应到管理界面中配置的数据,java中使用 Config 表示这些配置
Config config = ConfigService.getConfig(namespace);
// 通过命名空间与config对象拿到PropertySource(可遍历的),添加到apollo启动配置对象中。
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
// 将组装好的组合配置源扔进spring的环境对象中,置于首位
environment.getPropertySources().addFirst(composite);
}
|
我们需要关注下CompositePropertySource.addPropertySource(PropertySource<?> propertySource)
方法:
1
2
3
4
5
|
public void addPropertySource(PropertySource<?> propertySource) {
this.propertySources.add(propertySource);
}
// 而这里的propertySources可以观察到是一个链表结构,保证了有序。
private final Set<PropertySource<?>> propertySources = new LinkedHashSet<>();
|
propertySources
在spring中表示多配置属性源,而这个initialize(ConfigurableEnvironment environment)
本质上就是把我们在Apollo
界面中配置的数据加载到spring中了。到这里,数据都加载了进来。
多个命名空间的配置配置源封装到了名为ApolloBootstrapPropertySources
的CompositePropertySource
中。根据上面注释说明,propertySources
添加子项的时候通过LinkedHashSet
保证了有序。
而获取某个key对应属性值的方法是:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Override
@Nullable
public Object getProperty(String name) {
for (PropertySource<?> propertySource : this.propertySources) {
// 有序的集合中进行迭代,找到就返回,因此先添加进来的 PropertySource 会作为最终的值
Object candidate = propertySource.getProperty(name);
if (candidate != null) {
return candidate;
}
}
return null;
}
|
所以我们当时那个工程中关于apollo.bootstrap.namespaces
的配置应该改为application,common
。
而这个实现的逻辑位于Spring
中对于环境配置源的CompositePropertySource
中,其中通过链表添加多个命名空间对应的PropertySource
对象,获取同个key值时,取最先加入的命名空间对应的配置。