Spring遠(yuǎn)程加載配置的實(shí)現(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,實(shí)現(xiàn)從遠(yuǎn)程加載配置都是通過ConfigurableEnvironment
和PropertySource
完成的,步驟如下:
- 遠(yuǎn)程拉取配置,生成
PropertySource
- 從
ConfigurableEnvironment
獲取聚合類MutablePropertySources propertySources = ConfigurableEnvironment#getPropertySources();
- 將拉取的
PropertySource
添加到從ConfigurableEnvironment
獲取的聚合類MutablePropertySources#add...(PropertySource<?> propertySource)
至于這個(gè)過程是怎么觸發(fā)和運(yùn)行的,要看具體實(shí)現(xiàn)。
- 在apollo-client中,使用BeanFactoryPostProcessor。
- 在spring-cloud-starter-alibaba-nacos-config中,由于 cloud-nacos實(shí)現(xiàn)了spring cloud config規(guī)范(處于
org.springframework.cloud.bootstrap.config
包下),nacos實(shí)現(xiàn)該規(guī)范即可,即實(shí)現(xiàn)spring cloud 的PropertySourceLocator
接口。
Apollo
關(guān)注PropertySourcesProcessor
,該類為一個(gè)BeanFactoryPostProcessor
,同時(shí)為了獲取ConfigurableEnvironment
,該類實(shí)現(xiàn)了EnvironmentAware
回調(diào)接口。該類何時(shí)被加入spring容器?是通過@EnableApolloConfig
的@Import
注解的類ApolloConfigRegistrar
來加入,常規(guī)套路。
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, ApplicationEventPublisherAware, PriorityOrdered { // aware回調(diào)接口設(shè)置 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); // 從遠(yuǎn)程獲取PropertySource initializePropertySources(); // 為每個(gè)ConfigPropertySource注冊(cè)ConfigChangeEvent監(jiān)聽器 // 監(jiān)聽器監(jiān)聽到ConfigChangeEvent后publish一個(gè)ApolloConfigChangeEvent // 等于將apollo自定義的ConfigChangeEvent事件機(jī)制轉(zhuǎn)化為了spring的ApolloConfigChangeEvent事件 initializeAutoUpdatePropertiesFeature(beanFactory); } private void initializePropertySources() { // 聚合類,該類也是一個(gè)PropertySource,代理了一堆PropertySource // 該類中有一個(gè) Set<PropertySource<?>> 字段 CompositePropertySource composite = new ...; ... // 從 遠(yuǎn)程 或 本地緩存 獲取配置 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)); // 注冊(cè)監(jiān)聽器到每個(gè)PropertySource List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources(); for (ConfigPropertySource configPropertySource : configPropertySources) { configPropertySource.addChangeListener(configChangeEventPublisher); } } ... }
從上面可知初始化時(shí)會(huì)從ConfigService遠(yuǎn)程拉取配置,并保存到內(nèi)部緩存。而后續(xù)遠(yuǎn)程配置中心配置發(fā)生變化時(shí)本地會(huì)拉去最新配置并發(fā)布事件,PropertySource根據(jù)事件進(jìn)行更新。
無論是開始從遠(yuǎn)程拉取配置初始化,還是后續(xù)遠(yuǎn)程配置更新,最終都是通過RemoteConfigRepository
以http形式定時(shí)獲取配置:
public class RemoteConfigRepository extends AbstractConfigRepository implements ConfigRepository{ public RemoteConfigRepository(String namespace) { ... // 定時(shí)拉取 this.schedulePeriodicRefresh(); // 長輪詢 this.scheduleLongPollingRefresh(); ... } private void schedulePeriodicRefresh() { // 定時(shí)線程池 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() { // 事務(wù) Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig"); try { ApolloConfig previous = m_configCache.get(); // http遠(yuǎn)程拉取配置 ApolloConfig current = loadApolloConfig(); // reference equals means HTTP 304 if (previous != current) { logger.debug("Remote Config refreshed!"); // 設(shè)置緩存 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(); } ... }
可以看到,在構(gòu)造方法中,就執(zhí)行了 3 個(gè)本地方法,其中就包括定時(shí)刷新和長輪詢刷新。這兩個(gè)功能在 apollo 的 github 文檔中也有介紹:
- 客戶端和服務(wù)端保持了一個(gè)長連接,從而能第一時(shí)間獲得配置更新的推送。
- 客戶端還會(huì)定時(shí)從Apollo配置中心服務(wù)端拉取應(yīng)用的最新配置。
- 這是一個(gè)fallback機(jī)制,為了防止推送機(jī)制失效導(dǎo)致配置不更新。
- 客戶端定時(shí)拉取會(huì)上報(bào)本地版本,所以一般情況下,對(duì)于定時(shí)拉取的操作,服務(wù)端都會(huì)返回304 - Not Modified。
- 定時(shí)頻率默認(rèn)為每5分鐘拉取一次,客戶端也可以通過在運(yùn)行時(shí)指定System Property: apollo.refreshInterval來覆蓋,單位為分鐘。
所以,長連接是更新配置的主要手段,然后用定時(shí)任務(wù)輔助長連接,防止長連接失敗。
org.springframework.cloud.bootstrap.config
nacos實(shí)現(xiàn)了spring cloud config規(guī)范,規(guī)范代碼的maven坐標(biāo)如下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> <version>...</version> <scope>compile</scope> </dependency>
這里介紹規(guī)范內(nèi)容,nacos的實(shí)現(xiàn)略。
PropertySource
PropertySource
用于存儲(chǔ)k-v鍵值對(duì),遠(yuǎn)程或本地的配置最終都轉(zhuǎn)化為PropertySource
,放入ConfigurableEnvironment
中,通常EnumerablePropertySource
中會(huì)代理一個(gè)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 { // 實(shí)現(xiàn)類實(shí)現(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)用實(shí)現(xiàn)類 PropertySource<?> propertySource = locator.locate(environment); if (propertySource == null) { return Collections.emptyList(); } // 如果該P(yáng)ropertySource是代理了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); ... } } }
總結(jié)
可以看到從遠(yuǎn)程獲取配置都是通過向ConfigurableEnvironment
插入從遠(yuǎn)程獲取的數(shù)據(jù)轉(zhuǎn)化的PropertySource
。而從遠(yuǎn)程獲取就涉及到長輪詢、本地緩存等內(nèi)容,設(shè)計(jì)都比較一致。
到此這篇關(guān)于Spring遠(yuǎn)程加載配置的實(shí)現(xiàn)方法詳解的文章就介紹到這了,更多相關(guān)Spring遠(yuǎn)程加載配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot整合SpringSecurity的完整案例詳解
Spring Security是基于Spring生態(tài)圈的,用于提供安全訪問控制解決方案的框架,Spring Security登錄認(rèn)證主要涉及兩個(gè)重要的接口 UserDetailService和UserDetails接口,本文對(duì)Springboot整合SpringSecurity過程給大家介紹的非常詳細(xì),需要的朋友參考下吧2024-01-01關(guān)于IDEA報(bào)錯(cuò)Error:java 不支持發(fā)行版本17的原因及解決方案
在rebuild或運(yùn)行項(xiàng)目時(shí)提示“Error:java: 錯(cuò)誤: 不支持發(fā)行版本 17”,本文將給大家介紹了IDEA提示“Error:java: 錯(cuò)誤: 不支持發(fā)行版本17”的原因及解決方案,需要的朋友可以參考下2023-09-09Java如何利用狀態(tài)模式(state pattern)替代if else
這篇文章主要給大家介紹了關(guān)于Java如何利用狀態(tài)模式(state pattern)替代if else的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11SpringBoot在項(xiàng)目停止(服務(wù)停止/關(guān)閉退出)之后執(zhí)行的方法
這篇文章主要給大家介紹了SpringBoot在項(xiàng)目停止(服務(wù)停止/關(guān)閉退出)之后執(zhí)行的兩種方法,實(shí)現(xiàn)DisposableBean接口和使用@PreDestroy注解,文中有詳細(xì)的代碼講解,具有一定的參考價(jià)值,需要的朋友可以參考下2023-12-12MyBatis動(dòng)態(tài)SQL與緩存原理深入分析
這篇文章主要介紹了MyBatis動(dòng)態(tài)SQL與緩存原理,Mybatis框架的動(dòng)態(tài)SQL技術(shù)是一種根據(jù)特定條件動(dòng)態(tài)拼裝SQL語句的功能,它存在的意義是為了解決拼接SQL語句字符串時(shí)的痛點(diǎn)問題2023-02-02