为什么需要让本地配置优先于 apollo 呢?当你在本地调试时,开发环境的配置可能无法满足调试条件,例如要升级 mysql,需要在本地调试访问高版本的 mysql,但开发环境的 mysql 版本较低,或者要本地调试访问一个新版的 rpc 接口,或者是要调试访问一个 mock 接口,为了能满足调试条件而不改动开发环境配置影响其他童鞋使用,需要本地配置优先于 apollo 配置。
apollo 配置加载
apollo 主要有两类配置源,分别是 ApolloBootstrapPropertySources 和 ApolloPropertySources,ApolloBootstrapPropertySources 主要是提供 bean 工厂初始化时所需的配置参数,例如在 @condition 中使用 apollo 中的配置,ApolloPropertySources 主要供 bean 容器内的 bean 实例使用。
接下先看看 ApolloBootstrapPropertySources 的加载时机
// com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
@Override
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
// apollo.bootstrap.enabled = true 才支持
String enabled = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "false");
if (!Boolean.valueOf(enabled)) {
return;
}
// 获取需要加载的namespaces
String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
for (String namespace : namespaceList) {
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
// 添加到environment动态配置源的首位
environment.getPropertySources().addFirst(composite);
}
ApolloApplicationContextInitializer 实现了 ApplicationContextInitializer,在 SpringApplication#prepareContext 中执行。根据 apollo.bootstrap.enabled 控制是否开启加载,及根据 apollo.bootstrap.namespaces 决定要加载哪些 namespaces。
接下来看看
// com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (INITIALIZED.compareAndSet(false, true)) {
// 初始化配置源
initializePropertySources();
// 初始化热更新相关功能
initializeAutoUpdatePropertiesFeature(beanFactory);
}
}
private void initializePropertySources() {
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
//already initialized
return;
}
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);
//sort by order asc
ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
Iterator<Integer> iterator = orders.iterator();
while (iterator.hasNext()) {
int order = iterator.next();
for (String namespace : NAMESPACE_NAMES.get(order)) {
Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
}
// add after the bootstrap property source or to the first
if (environment.getPropertySources()
.contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
environment.getPropertySources()
.addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
} else {
environment.getPropertySources().addFirst(composite);
}
}
PropertySourcesProcessor 继承自 BeanFactoryPostProcessor,会在 bean 工厂初始化完成后,bean 扫描前完成配置源的加载,并且配置源会在 ApolloBootstrapPropertySources 之后。
添加本地配置源
知道了 apollo 配置源的加载时机和在环境对象的可变配置源集合的位置之后,就能很容易想到,只要在 bean 初始化前在 ApolloBootstrapPropertySources 前添加本地的配置源即可实现本地配置覆盖 apollo 配置。
@Component
@ConditionalOnProperty(
name = {"localCoverApollo"},
havingValue = "true"
)
public class ApolloLocalPropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
private static final Logger log = LoggerFactory.getLogger(ApolloLocalPropertySourcesProcessor.class);
private ConfigurableEnvironment environment;
public ApolloLocalPropertySourcesProcessor() {
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource("application.properties"));
PropertiesPropertySource propertySource = new PropertiesPropertySource("local", properties);
this.environment.getPropertySources().addFirst(propertySource);
log.info("本地配置覆盖apollo配置成功,本地配置:{}", properties);
} catch (Exception var4) {
Exception e = var4;
log.warn("本地配置覆盖apollo配置失败", e);
}
}
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment)environment;
}
}
以上是具体实现,总体来说还是非常简单的。如果对上面的代码还有疑问,可以研究一下 Enviroment 获取配置的实现。