您好,登錄后才能下訂單哦!
本篇內容主要講解“Spring遠程加載配置如何實現”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Spring遠程加載配置如何實現”吧!
本文以攜程的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,實現從遠程加載配置都是通過ConfigurableEnvironment
和PropertySource
完成的,步驟如下:
遠程拉取配置,生成PropertySource
從ConfigurableEnvironment
獲取聚合類 MutablePropertySources propertySources = ConfigurableEnvironment#getPropertySources();
將拉取的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
接口。
關注PropertySourcesProcessor
,該類為一個BeanFactoryPostProcessor
,同時為了獲取ConfigurableEnvironment
,該類實現了EnvironmentAware
回調接口。該類何時被加入spring容器?是通過@EnableApolloConfig
的@Import
注解的類ApolloConfigRegistrar
來加入,常規套路。
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, ApplicationEventPublisherAware, PriorityOrdered { // aware回調接口設置 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監聽器 // 監聽器監聽到ConfigChangeEvent后publish一個ApolloConfigChangeEvent // 等于將apollo自定義的ConfigChangeEvent事件機制轉化為了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; } // 定義監聽器,監聽器監聽到ConfigChangeEvent后發布ApolloConfigChangeEvent ConfigChangeListener configChangeEventPublisher = changeEvent -> applicationEventPublisher.publishEvent(new ApolloConfigChangeEvent(changeEvent)); // 注冊監聽器到每個PropertySource List<ConfigPropertySource> 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() { @Override public void run() { // 調用父抽象類trySync() // trySync()調用模版方法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); // 發布事件,該方法在父抽象類中 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 文檔中也有介紹:
客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。
客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
這是一個fallback機制,為了防止推送機制失效導致配置不更新。
客戶端定時拉取會上報本地版本,所以一般情況下,對于定時拉取的操作,服務端都會返回304 - Not Modified。
定時頻率默認為每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位為分鐘。
所以,長連接是更新配置的主要手段,然后用定時任務輔助長連接,防止長連接失敗。
org.springframework.cloud.bootstrap.config
nacos實現了spring cloud config規范,規范代碼的maven坐標如下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> <version>...</version> <scope>compile</scope> </dependency>
這里介紹規范內容,nacos的實現略。
PropertySource
用于存儲k-v鍵值對,遠程或本地的配置最終都轉化為PropertySource
,放入ConfigurableEnvironment
中,通常EnumerablePropertySource
中會代理一個PropertySource
的list。
規范接口主要為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<PropertySource<?>> locateCollection(Environment environment) { return locateCollection(this, environment); } static Collection<PropertySource<?>> 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<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); } } }
調用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由回調接口提供 ConfigurableEnvironment environment = applicationContext.getEnvironment(); for (PropertySourceLocator locator : this.propertySourceLocators) { // 調用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(); ... // 執行插入到ConfigurableEnvironment的MutablePropertySources insertPropertySources(propertySources, composite); ... } } }
到此,相信大家對“Spring遠程加載配置如何實現”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。