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

本文以携程的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。而从远程获取就涉及到长轮询、本地缓存等内容,设计都比较一致。

相关内容

热门资讯

原创 茅... 昨天和朋友一块喝酒的时候听到一个段子,说为什么茅台卖不动了?因为买的人破产了,喝的人进去了,其他人玩...
朱传华开展重大项目督导、重点旅... 根据《2025年下半年拼经济、稳增长工作机制》和《三亚市重点旅游景区市级领导包联包保服务工作方案》的...
旅途风景不止是眼睛看到的,更是... 于旅程期间,我们时常会被“风景”这俩字给吸引住,然而真正留存于记忆深处的,常常是超越了视觉所涵盖的范...
旅途风景超独特,涵盖多感官体验... 车窗外映入眼帘的画面,仅是一段旅程其中的风景,其内涵可不简单。它把所有感官收获的体验都涵盖了。视觉的...
旅途风景不在热门景点,这些发现... 身处旅行期间所见到的景致,绝非仅仅局限于相机镜头里所呈现出的画面,它实则是周遭环境、内心心境以及过往...