Spring遠程加載配置的實現(xiàn)方法詳解
前要
pom中引入一下依賴:
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.1</version>
</dependency>
不管是Apollo還是Nacos,實現(xiàn)從遠程加載配置都是通過ConfigurableEnvironment和PropertySource完成的,步驟如下:
- 遠程拉取配置,生成
PropertySource - 從
ConfigurableEnvironment獲取聚合類MutablePropertySources propertySources = ConfigurableEnvironment#getPropertySources(); - 將拉取的
PropertySource添加到從ConfigurableEnvironment獲取的聚合類MutablePropertySources#add...(PropertySource<?> propertySource)
至于這個過程是怎么觸發(fā)和運行的,要看具體實現(xiàn)。
- 在apollo-client中,使用BeanFactoryPostProcessor。
- 在spring-cloud-starter-alibaba-nacos-config中,由于 cloud-nacos實現(xiàn)了spring cloud config規(guī)范(處于
org.springframework.cloud.bootstrap.config包下),nacos實現(xiàn)該規(guī)范即可,即實現(xiàn)spring cloud 的PropertySourceLocator接口。
Apollo
關注PropertySourcesProcessor ,該類為一個BeanFactoryPostProcessor,同時為了獲取ConfigurableEnvironment,該類實現(xiàn)了EnvironmentAware回調(diào)接口。該類何時被加入spring容器?是通過@EnableApolloConfig的@Import注解的類ApolloConfigRegistrar來加入,常規(guī)套路。
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware,
ApplicationEventPublisherAware, PriorityOrdered {
// aware回調(diào)接口設置
private ConfigurableEnvironment environment;
@Override
public void setEnvironment(Environment environment) {
//it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
this.environment = (ConfigurableEnvironment) environment;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 獲取配置
this.configUtil = ApolloInjector.getInstance(ConfigUtil.class);
// 從遠程獲取PropertySource
initializePropertySources();
// 為每個ConfigPropertySource注冊ConfigChangeEvent監(jiān)聽器
// 監(jiān)聽器監(jiān)聽到ConfigChangeEvent后publish一個ApolloConfigChangeEvent
// 等于將apollo自定義的ConfigChangeEvent事件機制轉(zhuǎn)化為了spring的ApolloConfigChangeEvent事件
initializeAutoUpdatePropertiesFeature(beanFactory);
}
private void initializePropertySources() {
// 聚合類,該類也是一個PropertySource,代理了一堆PropertySource
// 該類中有一個 Set<PropertySource<?>> 字段
CompositePropertySource composite = new ...;
...
// 從 遠程 或 本地緩存 獲取配置
Config config = ConfigService.getConfig(namespace);
// 適配Config到PropertySource,并加入聚合類
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
// 添加到ConfigurableEnvironment
environment.getPropertySources().addFirst(composite);
}
private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {
if (!AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {
return;
}
// 定義監(jiān)聽器,監(jiān)聽器監(jiān)聽到ConfigChangeEvent后發(fā)布ApolloConfigChangeEvent
ConfigChangeListener configChangeEventPublisher = changeEvent ->
applicationEventPublisher.publishEvent(new ApolloConfigChangeEvent(changeEvent));
// 注冊監(jiān)聽器到每個PropertySource
List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
for (ConfigPropertySource configPropertySource : configPropertySources) {
configPropertySource.addChangeListener(configChangeEventPublisher);
}
}
...
}從上面可知初始化時會從ConfigService遠程拉取配置,并保存到內(nèi)部緩存。而后續(xù)遠程配置中心配置發(fā)生變化時本地會拉去最新配置并發(fā)布事件,PropertySource根據(jù)事件進行更新。
無論是開始從遠程拉取配置初始化,還是后續(xù)遠程配置更新,最終都是通過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() {
@Override
public void run() {
// 調(diào)用父抽象類trySync()
// trySync()調(diào)用模版方法sync()
trySync();
}
}, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),
m_configUtil.getRefreshIntervalTimeUnit());
}
@Override
protected synchronized void sync() {
// 事務
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");
try {
ApolloConfig previous = m_configCache.get();
// http遠程拉取配置
ApolloConfig current = loadApolloConfig();
// reference equals means HTTP 304
if (previous != current) {
logger.debug("Remote Config refreshed!");
// 設置緩存
m_configCache.set(current);
// 發(fā)布事件,該方法在父抽象類中
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();
}
...
}
可以看到,在構造方法中,就執(zhí)行了 3 個本地方法,其中就包括定時刷新和長輪詢刷新。這兩個功能在 apollo 的 github 文檔中也有介紹:
- 客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。
- 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 這是一個fallback機制,為了防止推送機制失效導致配置不更新。
- 客戶端定時拉取會上報本地版本,所以一般情況下,對于定時拉取的操作,服務端都會返回304 - Not Modified。
- 定時頻率默認為每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位為分鐘。
所以,長連接是更新配置的主要手段,然后用定時任務輔助長連接,防止長連接失敗。
org.springframework.cloud.bootstrap.config
nacos實現(xiàn)了spring cloud config規(guī)范,規(guī)范代碼的maven坐標如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>...</version>
<scope>compile</scope>
</dependency>
這里介紹規(guī)范內(nèi)容,nacos的實現(xiàn)略。
PropertySource
PropertySource用于存儲k-v鍵值對,遠程或本地的配置最終都轉(zhuǎn)化為PropertySource,放入ConfigurableEnvironment中,通常EnumerablePropertySource中會代理一個PropertySource的list。

PropertySourceLocator
規(guī)范接口主要為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 {
// 實現(xiàn)類實現(xiàn)該方法
PropertySource<?> locate(Environment environment);
default Collection<PropertySource<?>> locateCollection(Environment environment) {
return locateCollection(this, environment);
}
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator, Environment environment) {
// 調(diào)用實現(xiàn)類
PropertySource<?> propertySource = locator.locate(environment);
if (propertySource == null) {
return Collections.emptyList();
}
// 如果該PropertySource是代理了list的CompositePropertySource,提取全部
if (CompositePropertySource.class.isInstance(propertySource)) {
Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource).getPropertySources();
List<PropertySource<?>> filteredSources = new ArrayList<>();
for (PropertySource<?> p : sources) {
if (p != null) {
filteredSources.add(p);
}
}
return filteredSources;
}
else {
return Arrays.asList(propertySource);
}
}
}
PropertySourceBootstrapConfiguration
調(diào)用PropertySourceLocator接口將PropertySource加入ConfigurableEnvironment中。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
public void setPropertySourceLocators(Collection<PropertySourceLocator> propertySourceLocators) {
this.propertySourceLocators = new ArrayList<>(propertySourceLocators);
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
// 排序
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
// applicationContext由回調(diào)接口提供
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
// 調(diào)用PropertySourceLocator
Collection<PropertySource<?>> 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中的MutablePropertySources
MutablePropertySources propertySources = environment.getPropertySources();
...
// 執(zhí)行插入到ConfigurableEnvironment的MutablePropertySources
insertPropertySources(propertySources, composite);
...
}
}
}總結
可以看到從遠程獲取配置都是通過向ConfigurableEnvironment插入從遠程獲取的數(shù)據(jù)轉(zhuǎn)化的PropertySource。而從遠程獲取就涉及到長輪詢、本地緩存等內(nèi)容,設計都比較一致。
到此這篇關于Spring遠程加載配置的實現(xiàn)方法詳解的文章就介紹到這了,更多相關Spring遠程加載配置內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Springboot整合SpringSecurity的完整案例詳解
Spring Security是基于Spring生態(tài)圈的,用于提供安全訪問控制解決方案的框架,Spring Security登錄認證主要涉及兩個重要的接口 UserDetailService和UserDetails接口,本文對Springboot整合SpringSecurity過程給大家介紹的非常詳細,需要的朋友參考下吧2024-01-01
關于IDEA報錯Error:java 不支持發(fā)行版本17的原因及解決方案
在rebuild或運行項目時提示“Error:java: 錯誤: 不支持發(fā)行版本 17”,本文將給大家介紹了IDEA提示“Error:java: 錯誤: 不支持發(fā)行版本17”的原因及解決方案,需要的朋友可以參考下2023-09-09
Java如何利用狀態(tài)模式(state pattern)替代if else
這篇文章主要給大家介紹了關于Java如何利用狀態(tài)模式(state pattern)替代if else的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11
SpringBoot在項目停止(服務停止/關閉退出)之后執(zhí)行的方法
這篇文章主要給大家介紹了SpringBoot在項目停止(服務停止/關閉退出)之后執(zhí)行的兩種方法,實現(xiàn)DisposableBean接口和使用@PreDestroy注解,文中有詳細的代碼講解,具有一定的參考價值,需要的朋友可以參考下2023-12-12

