Spring-Cloud源码:加载Nacos远程配置到Spring容器中

文章目录

    • 1. NacosConfigBootstrapConfiguration
    • 2. PropertySourceBootstrapConfiguration
      • 1.1 注入PropertySourceLocator
      • 1.2 initialize
    • 3. NacosPropertySourceLocator
      • 3.1 locateCollection
      • 3.2 locate
      • 3.3 loadApplicationConfiguration
      • 3.4 loadNacosDataIfPresent
      • 3.4 loadNacosPropertySource
      • 3.5 NacosPropertySourceBuilder#build
      • 3.6 loadNacosData
      • 3.7 build
    • 4. PropertySourceBootstrapConfiguration
      • 4.1 initialize

文章将跟踪Spring-Cloud的源码,分析Nacos是如何将远程的配置文件加载到Spring的Environment中的

1. NacosConfigBootstrapConfiguration

NacosConfigBootstrapConfiguration在spring-cloud-starter-alibaba-nacos-config.jar包中的META-INF/spring.factories被定义,会被自动加载

NacosConfigBootstrapConfiguration是一个BootstrapConfiguration

BootstrapConfigurationBootstrapImportSelectorConfiguration所加载

BootstrapImportSelectorConfigurationBootstrapApplicationListener加载

这一块属于Spring-Cloud的源码知识,就不深究了

NacosConfigBootstrapConfiguration是一个Configuration,自然有向容器中注册Bean的能力

public class NacosConfigBootstrapConfiguration {
	
	// ...
	
	@Bean
	public NacosPropertySourceLocator nacosPropertySourceLocator(
			NacosConfigManager nacosConfigManager) {
		return new NacosPropertySourceLocator(nacosConfigManager);
	}

}

NacosConfigBootstrapConfiguration向容器中导入了NacosPropertySourceLocator

这一点非常重要,因为PropertySourceBootstrapConfiguration会用上NacosPropertySourceLocator

Spring-Cloud源码:加载Nacos远程配置到Spring容器中_第1张图片

2. PropertySourceBootstrapConfiguration

PropertySourceBootstrapConfiguration 是一个ApplicationContextInitializer

PropertySourceBootstrapConfiguration在spring-cloud-context.jar包中的META-INF/spring.factories被定义,会被自动加载

1.1 注入PropertySourceLocator

public class PropertySourceBootstrapConfiguration implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
		
	@Autowired(required = false)
	private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
	
	// ...
}

可以看到,PropertySourceBootstrapConfiguration 依赖Spring注入PropertySourceLocator

NacosPropertySourceLocator 就会被注入到propertySourceLocators 属性中

Spring-Cloud源码:加载Nacos远程配置到Spring容器中_第2张图片

SpringBoot准备好Environment和ApplicationContext后会回调ApplicationContextInitializerinitialize方法

整个核心逻辑就在PropertySourceBootstrapConfigurationinitialize方法中

1.2 initialize

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		
		List<PropertySource<?>> composite = new ArrayList<>();
		
		AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
		
		boolean empty = true;
		
		// 1. 提前保存environment属性, 等会需要对environment添加属性
		ConfigurableEnvironment environment = applicationContext.getEnvironment();
		
		for (PropertySourceLocator locator : this.propertySourceLocators) {
			
			// 2. 调用locator的locateCollection方法
			Collection<PropertySource<?>> source = locator.locateCollection(environment);
			
			// ...
		}
		// ....
	}

先不向下分析,而是去看看NacosPropertySourceLocator locateCollection方法

3. NacosPropertySourceLocator

3.1 locateCollection

locateCollection方法的实现在PropertySourceLocator接口中

	static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
			Environment environment) {
		
		// 调用子类的locate方法
		PropertySource<?> propertySource = locator.locate(environment);
		
		if (propertySource == null) {
			return Collections.emptyList();
		}
		
		// 如果是 { CompositePropertySource } 类型, 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);
		}
	}

继续跟踪locate方法

3.2 locate

	@Override
	public PropertySource<?> locate(Environment env) {
		
		// 1. { ConfigService } 用于读取nacos远程配置内容的, 属于nacos-api.jar包中的内容
		ConfigService configService = nacosConfigManager.getConfigService();

		if (null == configService) {
			log.warn("no instance of config service found, can't load config from nacos");
			return null;
		}
		
		long timeout = nacosConfigProperties.getTimeout();
		
		nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
				timeout);
		
		// 2. 获取bootstrap.yml中的 { spring.cloud.nacos.config.name }
		String name = nacosConfigProperties.getName();
		
		// 3. 获取bootstrap.yml中的 { spring.cloud.nacos.config.prefix }
		String dataIdPrefix = nacosConfigProperties.getPrefix();
		
		// 4. 如果没有配置{ spring.cloud.nacos.config.name }
		// 那么 { dataIdPrefix } 使用 { spring.cloud.nacos.config.prefix }
		if (StringUtils.isEmpty(dataIdPrefix)) {
			dataIdPrefix = name;
		}
		
		// 5. 如果也没有配置{ spring.cloud.nacos.config.prefix }
		// 那么 { dataIdPrefix } 使用 { spring.application.name }
		if (StringUtils.isEmpty(dataIdPrefix)) {
			dataIdPrefix = env.getProperty("spring.application.name");
		}
		
		// 6. 创建对象, 用于返回
		CompositePropertySource composite = new CompositePropertySource(
				NACOS_PROPERTY_SOURCE_NAME);

		// 7. 根据 { spring.cloud.nacos.config.shared-configs } 加载 nacos-config
		loadSharedConfiguration(composite);

		// 8. 根据 { spring.cloud.nacos.config.extension-configs } 加载 nacos-config		
		loadExtConfiguration(composite);
		
		// 9. 根据 { dataIdPrefix } 加载 nacos-config		
		loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
		
		// 10. 加载完成后, 返回CompositePropertySource
		return composite;
	}

==================================================================

{ spring.cloud.nacos.config.shared-configs }{ spring.cloud.nacos.config.extension-configs }

用于加载额外的配置,应用场景比较少,且loadSharedConfigurationloadExtConfiguration两个方法的逻辑和

loadApplicationConfiguration基本相似,就不细究这两个方法了

==================================================================

熟悉Nacos-Config都应该知道,nacos-config由两部分组成
- dataId
- group

而接下来从Nacos-Config获取配置的dataId就由dataIdPrefix作为前缀组成的

dataIdPrefix取值逻辑如下

Spring-Cloud源码:加载Nacos远程配置到Spring容器中_第3张图片

接下来继续跟踪loadApplicationConfiguration方法

3.3 loadApplicationConfiguration

	private void loadApplicationConfiguration(
			CompositePropertySource compositePropertySource, String dataIdPrefix,
			NacosConfigProperties properties, Environment environment) {
		
		// 1. 获取bootstrap.yml中的 { spring.cloud.nacos.config.fileExtension }
		// 如果没有配置, 默认值为: "properties"
		String fileExtension = properties.getFileExtension();
		
		// 2. 获取bootstrap.yml中的 { spring.cloud.nacos.config.group } 
		// 如果没有配置, 默认值为: "DEFAULT_GROUP"
		String nacosGroup = properties.getGroup();
		
		// 3. 通过 { dataId = dataIdPrefix , group = nacosGroup } 加载naocs配置
		loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
				fileExtension, true);
		
		// 4. 通过 { dataId = dataIdPrefix.fileExtension , group = nacosGroup } 加载naocs配置
		loadNacosDataIfPresent(compositePropertySource,
				dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);

		// 5. 通过 { dataId = dataIdPrefix-activeProfile.fileExtension , group = nacosGroup } 加载naocs配置		
		for (String profile : environment.getActiveProfiles()) {
		
			String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
		
			loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
					fileExtension, true);
		}

	}

Spring-Cloud源码:加载Nacos远程配置到Spring容器中_第4张图片

3.4.5都调用了同一个方法loadNacosDataIfPresent

其区别就在于,dataId不同

3.4 loadNacosDataIfPresent

	private void loadNacosDataIfPresent(final CompositePropertySource composite,
			final String dataId, final String group, String fileExtension,
			boolean isRefreshable) {
		
		// 1. 调用loadNacosPropertySource方法
		NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
				fileExtension, isRefreshable);
		
		// 2. 将 { propertySource  } 添加到 { composite } 中, 先有个印象, 分析完第一步可以再回来看
		// ps: 之前说越往下优先级越高: 就是因为这里采取的是头插法
		this.addFirstPropertySource(composite, propertySource, false);
	}

3.4 loadNacosPropertySource

	private NacosPropertySource loadNacosPropertySource(final String dataId,
			final String group, String fileExtension, boolean isRefreshable) {
		
		// 忽略, 和本次加载远程配置无关
		if (NacosContextRefresher.getRefreshCount() != 0) {
			if (!isRefreshable) {
				return NacosPropertySourceRepository.getNacosPropertySource(dataId,
						group);
			}
		}
		
		// 委托给了 { nacosPropertySourceBuilder }
		return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
				isRefreshable);
	}

3.5 NacosPropertySourceBuilder#build

	NacosPropertySource build(String dataId, String group, String fileExtension,
			boolean isRefreshable) {
		
		// 1. 调用 configServier加载远程配置
		Map<String, Object> data = loadNacosData(dataId, group, fileExtension);
		
		// ... 下面的待会再看
		NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
				data, new Date(), isRefreshable);
				
		NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
		
		return nacosPropertySource;
	}

3.6 loadNacosData

	private Map<String, Object> loadNacosData(String dataId, String group,
			String fileExtension) {
		
		// 1. 核心: 调用 { configService } 通过 { dataId } 和 { group } 获取配置信息
		String data = configService.getConfig(dataId, group, timeout);
		
		// 2. 将字符串转换为 { Map }
		Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
					.parseNacosData(data, fileExtension);
		
		// 3. 返回 { Map }
		return dataMap == null ? EMPTY_MAP : dataMap;
	}

================================================================

loadNacosData通过configServer加载远程配置

这个并不是长轮询,只是一个http请求,请求指定dataId和group对应的内容

这块属于Nacos的源码,在本章中就不继续深究了

================================================================

那么继续回到NacosPropertySourceBuilder#build方法

接着往下分析

3.7 build

	NacosPropertySource build(String dataId, String group, String fileExtension,
			boolean isRefreshable) {
		
		// 1. 调用 configServier加载远程配置
		Map<String, Object> data = loadNacosData(dataId, group, fileExtension);
		
		// 上面的已经分析过了 
		// =====================================================================
		
		// 2. 将Map包装成NacosPropertySource 
		NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
				data, new Date(), isRefreshable);
				
		
		// 3. 返回
		return nacosPropertySource;
	}
  1. configService加载的远程配置包装成Map
  2. Map包装成NacosPropertySource
  3. 返回的nacosPropertySource, 将会被添加到 composite集合中,回顾loadNacosDataIfPresent

============================================================

NacosPropertySource 实现了PropertySource接口

实际上,Spring的Environment就是多个PropertySource的集合体

该方法返回的NacosPropertySource 待会就会被放入到Environment中

============================================================

现在,我们已经分析完了,Spring是如何获取到Nacos配置的

接下来,将继续分析,从Nacos获取到的配置,是如何放入Spring的Environment中的

  1. 在3.2中,将nacos配置包装成NacosPropertySource 放入到composite集合中,并且将composite集合返回
  2. 在3.1中,将composite集合返回给PropertySourceBootstrapConfigurationinitialize方法中

Spring-Cloud源码:加载Nacos远程配置到Spring容器中_第5张图片
那么我们回到initialize方法,接着往下看未分析的代码

4. PropertySourceBootstrapConfiguration

4.1 initialize

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		List<PropertySource<?>> composite = new ArrayList<>();
		
		AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
		
		boolean empty = true;
		
		ConfigurableEnvironment environment = applicationContext.getEnvironment();
		
		for (PropertySourceLocator locator : this.propertySourceLocators) {
			
			Collection<PropertySource<?>> source = locator.locateCollection(environment);
			
			// 上面是已经分析完的
			// ================================================================
			List<PropertySource<?>> sourceList = new ArrayList<>();
			
			// 1. source集合, 将PropertySource封装成BootstrapPropertySource
			// 2. 放入到sourceList集合中
			for (PropertySource<?> p : source) {
				sourceList.add(new BootstrapPropertySource<>(p));
			}
			
			// 3. 放入到composite集合中
			composite.addAll(sourceList);
			
			empty = false;
		}
		if (!empty) {
				
			MutablePropertySources propertySources = environment.getPropertySources();
			
			// 4. 核心方法: 之前说过 { environment } 就是多个 { propertySource } 的集合
			// { insertPropertySources } 方法就是将 { composite } 集合放入到 { propertySources } 集合中
			insertPropertySources(propertySources, composite);
			
			// ...
		}
	}

insertPropertySources的方法细节就不去深究了,其目的就是将将composite集合放入到propertySources集合中

propertySources来自environment修改了propertySources就相当于修改了environment

至此:如何从Nacos读取配置放入到Spring的Environment的源码分析就结束了

你可能感兴趣的:(spring,java,spring,boot)