从源码角度分析 Nacos 配置文件加载以及加载优先级

      • 前言
      • 核心类 NacosPropertySourceLocator 加载过程
      • 核心方法 NacosPropertySourceLocator#locate
      • 总结

前言

源码版本以 2021.0.1.0 版本进行分析:spring-cloud-starter-alibaba-nacos-config-2021.0.1.0

<dependency>
  <groupId>com.alibaba.cloudgroupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
  <version>2021.0.1.0version>
dependency>

核心类 NacosPropertySourceLocator 加载过程

当项目中引入了当前依赖,可以查看引入的依赖信息,关心它自动装配的类,规则加载流程主要关注这个类
从源码角度分析 Nacos 配置文件加载以及加载优先级_第1张图片

// 对 @Bean 注解标注方法不通过代理调用
@Configuration(proxyBeanMethods = false)
// spring.cloud.nacos.config.enabled 默认为 true
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public NacosConfigProperties nacosConfigProperties() {
    // spring.cloud.nacos.config 前缀的所有配置内容,都存储在这个类下面
		return new NacosConfigProperties();
	}

	@Bean
	@ConditionalOnMissingBean
	public NacosConfigManager nacosConfigManager(
			NacosConfigProperties nacosConfigProperties) {
    // 用于管理配置内容,创建 Nacos 客户端动态刷新的核心类以及设置动态刷新的监听
		return new NacosConfigManager(nacosConfigProperties);
	}

	@Bean
	public NacosPropertySourceLocator nacosPropertySourceLocator(
			NacosConfigManager nacosConfigManager) {
		// 属性配置文件解析
		return new NacosPropertySourceLocator(nacosConfigManager);
	}

}

NacosPropertySourceLocator 类是处理配置规则加载的核心类;NacosConfigProperties 类是基础配置类存储配置信息的;NacosConfigManager 是用于所有配置源的,会使用它来创建动态配置的监听以及 Nacos 客户端核心类

NacosConfigManager 依赖于 NacosConfigProperties,会优先注入

NacosPropertySourceLocator 依赖于 NacosConfigManager,会优先注入

加载配置的方法入口:NacosPropertySourceLocator#locate,首先梳理该方法是如何被调用的,Debug 断点在该方法上,启动应用,就可以看到下面调用的方法堆栈信息:
从源码角度分析 Nacos 配置文件加载以及加载优先级_第2张图片
流程图梳理如下:

从源码角度分析 Nacos 配置文件加载以及加载优先级_第3张图片

核心方法 NacosPropertySourceLocator#locate

在这里主要介绍的是 NacosPropertySourceLocator 类,看它是如何加载配置文件内容以及配置文件优先级处理的?

到这里,开始分析 NacosPropertySourceLocator#locate 方法源码的处理过程了

public PropertySource<?> locate(Environment env) {
  nacosConfigProperties.setEnvironment(env);
  ConfigService configService = nacosConfigManager.getConfigService();
  if (null == configService) {
    log.warn("no instance of config service found, can't load config from nacos");
    return null;
  }
  // 设置读取配置的超时时间,默认为 3000 毫秒
  long timeout = nacosConfigProperties.getTimeout();
  nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
  String name = nacosConfigProperties.getName();
  // 优先获取此属性:spring.cloud.nacos.config.prefix
  String dataIdPrefix = nacosConfigProperties.getPrefix();
  if (StringUtils.isEmpty(dataIdPrefix)) {
    // 未配置取:spring.cloud.nacos.config.name
    dataIdPrefix = name;
  }
  if (StringUtils.isEmpty(dataIdPrefix)) {
    // 未配置取:spring.application.name
    dataIdPrefix = env.getProperty("spring.application.name");
  }
  CompositePropertySource composite = new CompositePropertySource(
    NACOS_PROPERTY_SOURCE_NAME);
  // 优先级在这里体现:shared-configs < ext-configs < 当前项目配置
  // 当前项目配置内也存在一个优先级:default < 扩展符配置 < 环境标识符配置
  // 在这些配置文件中设置了 refresh=true 标识,才进行动态刷新,先会追加到监听器中
  loadSharedConfiguration(composite);
  loadExtConfiguration(composite);
  loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
  // 组合所有读取到的配置文件返回,后续再 Spring 子容器中调用 refresh 方法时再填充属性阶段进行赋值.
  return composite;
}

此方法加载了在 bootstrap 中配置的文件,应用名|应用名+后缀|应用名+profile+后缀、shared-configs、extension-configs

从源码上就可以看出,最后加载的文件肯定是优先级最高的,即 loadApplicationConfiguration 方法,该方法内又区分了应用内多个配置文件下的优先级:应用名 < 应用名+后缀 < 应用名+profile+后缀

/**
 * 加载 shared-configs 配置
 */
private void loadSharedConfiguration(
  CompositePropertySource compositePropertySource) {
  List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties
    .getSharedConfigs();
  if (!CollectionUtils.isEmpty(sharedConfigs)) {
    checkConfiguration(sharedConfigs, "shared-configs");
    loadNacosConfiguration(compositePropertySource, sharedConfigs);
  }
}

/**
 * 加载 extension-configs 配置
 */
private void loadExtConfiguration(CompositePropertySource compositePropertySource) {
  List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties
    .getExtensionConfigs();
  if (!CollectionUtils.isEmpty(extConfigs)) {
    checkConfiguration(extConfigs, "extension-configs");
    loadNacosConfiguration(compositePropertySource, extConfigs);
  }
}

/**
 * 加载应用内配置
 */
private void loadApplicationConfiguration(
  CompositePropertySource compositePropertySource, String dataIdPrefix,
  NacosConfigProperties properties, Environment environment) {
  // 不设置,默认值:properties
  String fileExtension = properties.getFileExtension();
  String nacosGroup = properties.getGroup();
  // 加载默认的,不追加扩展符的 data-id 文件
  loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
                         fileExtension, true);
  // 加载带扩展符的 data-id 文件,优先级大于默认的
  loadNacosDataIfPresent(compositePropertySource,
                         dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
  // 加载带环境标识的 data-id 文件,优先级大于带扩展符的
  for (String profile : environment.getActiveProfiles()) {
    String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
    loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
                           fileExtension, true);
  }
}

加载 shared-configs、extension-configs 配置时会去检测 dataId 是否为空,为空就会出现异常

/**
 * 检查配置是否有问题,dataId 不允许为空
 */
private void checkConfiguration(List<NacosConfigProperties.Config> configs, String tips) {
  for (int i = 0; i < configs.size(); i++) {
    String dataId = configs.get(i).getDataId();
    if (dataId == null || dataId.trim().length() == 0) {
      throw new IllegalStateException(String.format(
        "the [ spring.cloud.nacos.config.%s[%s] ] must give a dataId",
        tips, i));
    }
  }
}

从源码角度分析 Nacos 配置文件加载以及加载优先级_第4张图片

以上源码图是加载属性的部分,在加载属性时,若已经动态刷新过了&未开启动态刷新,就会取用之前的数据;否则,就会调用 NacosPropertySourceBuilder#build 方法

NacosPropertySource build(String dataId, String group, String fileExtension,
      boolean isRefreshable) {
   // 调用 NacosConfigService.getConfig 方法获取 dataId 对应的配置内容
   List<PropertySource<?>> propertySources = loadNacosData(dataId, group, fileExtension);
   NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
         group, dataId, new Date(), isRefreshable);
   // 封装完对象后存入到 NacosPropertySourceRepository#NACOS_PROPERTY_SOURCE_REPOSITORY 集合中
   NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
   return nacosPropertySource;
}

从上面源码可以看出,若开启了刷新机制,就会往 NacosPropertySourceRepository#NACOS_PROPERTY_SOURCE_REPOSITORY 集合中塞数据;若未开启的话,就不会塞数据

NacosPropertySourceRepository#NACOS_PROPERTY_SOURCE_REPOSITORY 集合里的元素是后续用来注册监听器的,涉及到动态监听的部分了,这块后续有文章对整个动态刷新机制源码的讲解

无论其是否开启动态刷新,最终都会把数据添加到 CompositePropertySource#propertySources 集合中,它里面的元素最终用处在于: Bean 生命周期-填充属性阶段

到这里,整个配置规则加载的源码分析就完成了

总结

该篇文章从源码角度上分析了 Nacos 配置文件如何加载的过程以及常见的一些会出现在细节上忽略的问题,配置文件上加载的优先级:shared-configs、extension-configs、应用配置,最后对配置文件加载的入口以及配置规则解析的源码进行了分析,包括:动态刷新监听器的触发点->NacosPropertySourceRepository#NACOS_PROPERTY_SOURCE_REPOSITORY

更多技术文章可以查看:vnjohn 个人博客

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