您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Spring遠(yuǎn)程加載配置如何實(shí)現(xiàn)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Spring遠(yuǎn)程加載配置如何實(shí)現(xiàn)”吧!
本文以攜程的Apollo和阿里的Nacos為例。
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
接口。
關(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注冊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)); // 注冊監(jiān)聽器到每個(gè)PropertySource List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources(); for (ConfigPropertySource configPropertySource : configPropertySources) { configPropertySource.addChangeListener(configChangeEventPublisher); } } ... }
從上面可知初始化時(shí)會從ConfigService遠(yuǎn)程拉取配置,并保存到內(nèi)部緩存。而后續(xù)遠(yuǎn)程配置中心配置發(fā)生變化時(shí)本地會拉去最新配置并發(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í)間獲得配置更新的推送。
客戶端還會定時(shí)從Apollo配置中心服務(wù)端拉取應(yīng)用的最新配置。
這是一個(gè)fallback機(jī)制,為了防止推送機(jī)制失效導(dǎo)致配置不更新。
客戶端定時(shí)拉取會上報(bào)本地版本,所以一般情況下,對于定時(shí)拉取的操作,服務(wù)端都會返回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
用于存儲k-v鍵值對,遠(yuǎn)程或本地的配置最終都轉(zhuǎn)化為PropertySource
,放入ConfigurableEnvironment
中,通常EnumerablePropertySource
中會代理一個(gè)PropertySource
的list。
規(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); } } }
調(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); ... } } }
到此,相信大家對“Spring遠(yuǎn)程加載配置如何實(shí)現(xiàn)”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。