溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring遠(yuǎn)程加載配置如何實(shí)現(xiàn)

發(fā)布時(shí)間:2023-03-27 14:12:58 來源:億速云 閱讀:127 作者:iii 欄目:開發(fā)技術(shù)

本篇內(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)程加載配置都是通過ConfigurableEnvironmentPropertySource完成的,步驟如下:

  • 遠(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注冊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

PropertySource用于存儲k-v鍵值對,遠(yuǎn)程或本地的配置最終都轉(zhuǎn)化為PropertySource,放入ConfigurableEnvironment中,通常EnumerablePropertySource中會代理一個(gè)PropertySource的list。

Spring遠(yuǎn)程加載配置如何實(shí)現(xiàn)

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);
			...
		}
	}
}

到此,相信大家對“Spring遠(yuǎn)程加載配置如何實(shí)現(xiàn)”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(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)容。

AI