Spring 远程加载配置
创始人
2025-06-01 19:01:52
0

本文以携程的Apollo和阿里的Nacos为例。
pom中引入一下依赖:

        com.ctrip.framework.apolloapollo-client2.0.1com.alibaba.cloudspring-cloud-starter-alibaba-nacos-config2021.1

不管是Apollo还是Nacos,实现从远程加载配置都是通过ConfigurableEnvironmentPropertySource完成的,步骤如下:

  1. 远程拉取配置,生成PropertySource
  2. ConfigurableEnvironment获取聚合类 MutablePropertySources propertySources = ConfigurableEnvironment#getPropertySources();
  3. 将拉取的PropertySource添加到从ConfigurableEnvironment获取的聚合类MutablePropertySources#add...(PropertySource propertySource)

至于这个过程是怎么触发和运行的,要看具体实现。

  • apollo-client中,使用BeanFactoryPostProcessor。
  • spring-cloud-starter-alibaba-nacos-config中,由于 cloud-nacos实现了spring cloud config规范(处于org.springframework.cloud.bootstrap.config包下),nacos实现该规范即可,即实现spring cloud 的PropertySourceLocator接口。

Apollo

关注PropertySourcesProcessor ,该类为一个BeanFactoryPostProcessor,同时为了获取ConfigurableEnvironment,该类实现了EnvironmentAware回调接口。该类何时被加入spring容器?是通过@EnableApolloConfig@Import注解的类ApolloConfigRegistrar来加入,常规套路。

public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware,ApplicationEventPublisherAware, PriorityOrdered {// aware回调接口设置private ConfigurableEnvironment environment;@Overridepublic void setEnvironment(Environment environment) {//it is safe enough to cast as all known environment is derived from ConfigurableEnvironmentthis.environment = (ConfigurableEnvironment) environment;}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// 获取配置this.configUtil = ApolloInjector.getInstance(ConfigUtil.class);// 从远程获取PropertySourceinitializePropertySources();// 为每个ConfigPropertySource注册ConfigChangeEvent监听器// 监听器监听到ConfigChangeEvent后publish一个ApolloConfigChangeEvent// 等于将apollo自定义的ConfigChangeEvent事件机制转化为了spring的ApolloConfigChangeEvent事件initializeAutoUpdatePropertiesFeature(beanFactory);}private void initializePropertySources() {// 聚合类,该类也是一个PropertySource,代理了一堆PropertySource// 该类中有一个 Set> 字段CompositePropertySource composite = new ...;...// 从 远程 或 本地缓存 获取配置Config config = ConfigService.getConfig(namespace);// 适配Config到PropertySource,并加入聚合类		composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));// 添加到ConfigurableEnvironmentenvironment.getPropertySources().addFirst(composite);}private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {if (!AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {return;}// 定义监听器,监听器监听到ConfigChangeEvent后发布ApolloConfigChangeEventConfigChangeListener configChangeEventPublisher = changeEvent ->applicationEventPublisher.publishEvent(new ApolloConfigChangeEvent(changeEvent));// 注册监听器到每个PropertySourceList configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();for (ConfigPropertySource configPropertySource : configPropertySources) {configPropertySource.addChangeListener(configChangeEventPublisher);}}...
}

从上面可知初始化时会从ConfigService远程拉取配置,并保存到内部缓存。而后续远程配置中心配置发生变化时本地会拉去最新配置并发布事件,PropertySource根据事件进行更新。

无论是开始从远程拉取配置初始化,还是后续远程配置更新,最终都是通过RemoteConfigRepository以http形式定时获取配置:

public class RemoteConfigRepository extends AbstractConfigRepository implements ConfigRepository{public RemoteConfigRepository(String namespace) {...// 定时拉取this.schedulePeriodicRefresh();// 长轮询this.scheduleLongPollingRefresh();...}private void schedulePeriodicRefresh() {// 定时线程池m_executorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {// 调用父抽象类trySync()// trySync()调用模版方法sync()trySync();}}, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),m_configUtil.getRefreshIntervalTimeUnit());}@Overrideprotected synchronized void sync() {// 事务Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");try {ApolloConfig previous = m_configCache.get();// http远程拉取配置ApolloConfig current = loadApolloConfig();// reference equals means HTTP 304if (previous != current) {logger.debug("Remote Config refreshed!");// 设置缓存m_configCache.set(current);// 发布事件,该方法在父抽象类中this.fireRepositoryChange(m_namespace, this.getConfig());}if (current != null) {Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),current.getReleaseKey());}transaction.setStatus(Transaction.SUCCESS);} catch (Throwable ex) {transaction.setStatus(ex);throw ex;} finally {transaction.complete();}...}

可以看到,在构造方法中,就执行了 3 个本地方法,其中就包括定时刷新和长轮询刷新。这两个功能在 apollo 的 github 文档中也有介绍:

  1. 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。

  2. 客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。

  3. 这是一个fallback机制,为了防止推送机制失效导致配置不更新。

  4. 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified。

  5. 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。

所以,长连接是更新配置的主要手段,然后用定时任务辅助长连接,防止长连接失败。

org.springframework.cloud.bootstrap.config

nacos实现了spring cloud config规范,规范代码的maven坐标如下:

    org.springframework.cloudspring-cloud-context...compile

这里介绍规范内容,nacos的实现略。

PropertySource

PropertySource用于存储k-v键值对,远程或本地的配置最终都转化为PropertySource,放入ConfigurableEnvironment中,通常EnumerablePropertySource中会代理一个PropertySource的list。
在这里插入图片描述

PropertySourceLocator

规范接口主要为PropertySourceLocator接口,该接口用于定位PropertySource,注释如下:

Strategy for locating (possibly remote) property sources for the Environment. Implementations should not fail unless they intend to prevent the application from starting.

public interface PropertySourceLocator {// 实现类实现该方法PropertySource locate(Environment environment);default Collection> locateCollection(Environment environment) {return locateCollection(this, environment);}static Collection> locateCollection(PropertySourceLocator locator, Environment environment) {// 调用实现类PropertySource propertySource = locator.locate(environment);if (propertySource == null) {return Collections.emptyList();}// 如果该PropertySource是代理了list的CompositePropertySource,提取全部if (CompositePropertySource.class.isInstance(propertySource)) {Collection> sources = ((CompositePropertySource) propertySource).getPropertySources();List> filteredSources = new ArrayList<>();for (PropertySource p : sources) {if (p != null) {filteredSources.add(p);}}return filteredSources;}else {return Arrays.asList(propertySource);}}}

PropertySourceBootstrapConfiguration

调用PropertySourceLocator接口将PropertySource加入ConfigurableEnvironment中。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfigurationimplements ApplicationContextInitializer, Ordered {@Autowired(required = false)private List propertySourceLocators = new ArrayList<>();public void setPropertySourceLocators(Collection propertySourceLocators) {this.propertySourceLocators = new ArrayList<>(propertySourceLocators);}@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {List> composite = new ArrayList<>();// 排序AnnotationAwareOrderComparator.sort(this.propertySourceLocators);boolean empty = true;// applicationContext由回调接口提供ConfigurableEnvironment environment = applicationContext.getEnvironment();for (PropertySourceLocator locator : this.propertySourceLocators) {// 调用PropertySourceLocatorCollection> source = locator.locateCollection(environment);...for (PropertySource p : source) {// 是否代理了PropertySource的list做分类if (p instanceof EnumerablePropertySource) {EnumerablePropertySource enumerable = (EnumerablePropertySource) p;sourceList.add(new BootstrapPropertySource<>(enumerable));}else {sourceList.add(new SimpleBootstrapPropertySource(p));}}composite.addAll(sourceList);empty = false;}if (!empty) {// 获取 ConfigurableEnvironment中的MutablePropertySourcesMutablePropertySources propertySources = environment.getPropertySources();...// 执行插入到ConfigurableEnvironment的MutablePropertySourcesinsertPropertySources(propertySources, composite);...}}
}

总结

可以看到从远程获取配置都是通过向ConfigurableEnvironment插入从远程获取的数据转化的PropertySource。而从远程获取就涉及到长轮询、本地缓存等内容,设计都比较一致。

相关内容

热门资讯

原创 馅... #馅料里添这三样东西炸出焦脆鲜香的肉丸子,过年必备 在年味浓郁的氛围里,一道经典的炸肉丸子总能勾起...
从百年老店到现代产业!城关区推... 黄河穿城而过,牛肉拉面香飘百年。在兰州市城关区,这碗承载着城市记忆的面食,正经历着新变革:城关区以“...
试吃冰激凌遇到了超大方的店员 本文来自豆瓣小组“尸体暖暖的” 由豆瓣用户@我王多鱼投了 授权发布 感谢作者为豆瓣提供优质原创内容 ...
非洲再现致命狮子咬人事件 据英国《每日邮报》2日报道,纳米比亚西北部偏远地区的一处豪华度假村内,一头狮子咬死了一名男子。警方透...
蒸蛋羹出现蜂窝?水和蛋比例是秘... 每次看到饭店里那碗平滑如镜的蒸蛋羹,是不是总怀疑厨师偷偷施了魔法?自家做的不是变成"月球表面"就是成...
原创 官... 在外出游玩时,学会吃亏是一种福气,千万不要因一时冲动而放纵自己情绪的爆发。最近,网络上流传着一则“迪...
解放双手,无忧畅游!“轻松游河... 解放双手 无忧畅游 “轻松游河北”行李寄递服务上线 5月31日,在位于石家庄火车站的旅游服务中心内...
原创 官... “骂你一句傻x就破防了,跟你理论时还先动手打人。” 情侣和夫妻锁喉推搡混战3分钟,仅因为拍照嫌娃挡镜...
原创 精... 2025年科学指南:精准计算每日面粉摄入量的5个步骤 1652字 1. 确定每日总谷物需求 健康成年...
茶香飘出新韵味 在2025北京国际茶业展上,观众在张一元展台品尝新茶饮。经济日报记者 吉亚矫摄 绿茶幽香、红茶醇厚、...
原创 含... 俗话说 “气血不足百病生” ,气血是生命的根本,人体的五脏六腑、骨骼经络,乃至毛发皮肤都必须依赖气血...
原创 和... #和老公回婆家过年,一进门又是这个样子,“年味”还有吗? 作为一名常年在文字世界里游走的作家,我对...
原创 隆... #隆江猪脚的卤水怎么做更有香味,至少老师傅都是如此做的 香料,是卤水香味的重要源泉。八角、桂皮、香...
原创 这... #这道硬菜,撑起了大半个中国人年夜饭餐桌,少了它长辈会不高兴 在中国的年夜饭桌上,有一道菜堪称“硬...
原创 天... 进入夏日, 天气炎热,让人酷热难耐, 心情变得烦躁, 食欲不振,消化也变差,但为了身体的健康, 也不...
原创 这... #这样的火锅汤底清甜味美,清热去火,食材涮一涮就是丰盛的一顿 在饮食的世界里,火锅始终占据着独特的...
原创 时... 胃和心,总要有一个是满的。所以,全世界的迷茫和忧伤都可以用美食去抵挡!更多家常美食做法,请关注典典小...
左右一下刷一个圆蛋黄酥 还没到... 左右一下刷一个圆蛋黄酥 还没到中秋就馋月饼了 烘焙人的日常
被冤枉的枇杷梗 喧闹的枇杷季过去了,但在我脑海里,却仍盘旋着“枇杷梗”三个字且挥之不去。 苏州有一种松脆的零食也叫“...
南瓜小米馒头,营养丰富,蓬松宣... 南瓜小米馒头,营养丰富,蓬松宣软自制南瓜馒头 花式面点 馒头