Nacos源码学习(2)— spring cloud集成nacos配置中心

spring-cloud-starter-alibaba-nacos-config包提供了spring cloud集成nacos功能,对于spring boot项目,可以在项目启动时,直接去nacos配置中心拉取配置。

NacosConfigBootstrapConfiguration类会在项目开始启动时进行加载,在spring.factories里配置如下:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration

NacosConfigBootstrapConfiguration会加载三个Bean,NacosConfigProperties是用来加载nacos配置文件相关信息的,主要包括nacos访问地址,group、namespace等信息。NacosConfigManager会初始化nacos api SDK里的ConfigService类,后续跟nacos交互都是通过SDK里提供的这个对象。核心代码如下:

static ConfigService createConfigService(
      NacosConfigProperties nacosConfigProperties) {
   if (Objects.isNull(service)) {
      synchronized (NacosConfigManager.class) {
         try {
            if (Objects.isNull(service)) {
               service = NacosFactory.createConfigService(
                     nacosConfigProperties.assembleConfigServiceProperties());
            }
         }
         catch (NacosException e) {
            log.error(e.getMessage());
            throw new NacosConnectionFailureException(
                  nacosConfigProperties.getServerAddr(), e.getMessage(), e);
         }
      }
   }
   return service;
}

NacosPropertySourceLocator实现了PropertySourceLocator接口,是用来在项目启动时,从nacos拉取项目配置并解析后,写入到spring boot项目本地配置中。这个类加载启动时配置的核心类。核心代码如下:

@Override
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;
   }
   long timeout = nacosConfigProperties.getTimeout();
   nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
         timeout);
   String name = nacosConfigProperties.getName();
   
   String dataIdPrefix = nacosConfigProperties.getPrefix();
   if (StringUtils.isEmpty(dataIdPrefix)) {
      dataIdPrefix = name;
   }
   // dataId前缀为空的话,会取配置的名字,也为空的话,直接取spring配置的项目名
   if (StringUtils.isEmpty(dataIdPrefix)) {
      dataIdPrefix = env.getProperty("spring.application.name");
   }

   CompositePropertySource composite = new CompositePropertySource(
         NACOS_PROPERTY_SOURCE_NAME);
         //加载共享配置
   loadSharedConfiguration(composite);
   //加载扩展配置
   loadExtConfiguration(composite);
   //加载项目本身配置
   loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
   return composite;
}

加载各类配置逻辑类似,只看loadApplicationConfiguration方法。加载配置顺序如下,以dataId前缀为application,文件扩展名为yml,active profile为test为例,会先加载data id 为application的配置项,再加载application.yml,最后再加载application-test.yml。代码如下:

private void loadApplicationConfiguration(
      CompositePropertySource compositePropertySource, String dataIdPrefix,
      NacosConfigProperties properties, Environment environment) {
   String fileExtension = properties.getFileExtension();
   String nacosGroup = properties.getGroup();
   // load directly once by default
   loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
         fileExtension, true);
   // load with suffix, which have a higher priority than the default
   loadNacosDataIfPresent(compositePropertySource,
         dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
   // Loaded with profile, which have a higher priority than the suffix
   for (String profile : environment.getActiveProfiles()) {
      String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
      loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
            fileExtension, true);
   }

}

loadNacosDataIfPresent会调用nacosPropertySourceBuilder的build方法去nacos拉取配置,代码如下:

private void loadNacosDataIfPresent(final CompositePropertySource composite,
      final String dataId, final String group, String fileExtension,
      boolean isRefreshable) {
   if (null == dataId || dataId.trim().length() < 1) {
      return;
   }
   if (null == group || group.trim().length() < 1) {
      return;
   }
   //加载配置
   NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
         fileExtension, isRefreshable);
         //把nacos配置加载到spring boot配置中
   this.addFirstPropertySource(composite, propertySource, false);
}

private NacosPropertySource loadNacosPropertySource(final String dataId,
      final String group, String fileExtension, boolean isRefreshable) {
   if (NacosContextRefresher.getRefreshCount() != 0) {
       //如果不需要支持刷新功能,直接从缓存中取即可
      if (!isRefreshable) {
         return NacosPropertySourceRepository.getNacosPropertySource(dataId,
               group);
      }
   }
   //去nacos拉取配置,然后添加到本地缓存中
   return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
         isRefreshable);
}

build方法如下:

NacosPropertySource build(String dataId, String group, String fileExtension,
      boolean isRefreshable) {
          // 加载nacos配置
   List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
         fileExtension);
   NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
         group, dataId, new Date(), isRefreshable);
         //配置加入到本地缓存
   NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
   return nacosPropertySource;
}

private List<PropertySource<?>> loadNacosData(String dataId, String group,
      String fileExtension) {
   String data = null;
   try {
       //用nacos提供的api SDK拉取配置
      data = configService.getConfig(dataId, group, timeout);
      if (StringUtils.isEmpty(data)) {
         log.warn(
               "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",
               dataId, group);
         return Collections.emptyList();
      }
      if (log.isDebugEnabled()) {
         log.debug(String.format(
               "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
               group, data));
      }
      //根据不同类型的配置文件,选择相应的解析器来解析配置文件
      return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
            fileExtension);
   }
   catch (NacosException e) {
      log.error("get data from Nacos error,dataId:{} ", dataId, e);
   }
   catch (Exception e) {
      log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
   }
   return Collections.emptyList();
}

parseNacosData方法如下:

public List<PropertySource<?>> parseNacosData(String configName, String configValue,
      String extension) throws IOException {
   if (StringUtils.isEmpty(configValue)) {
      return Collections.emptyList();
   }
   if (StringUtils.isEmpty(extension)) {
      extension = this.getFileExtension(configName);
   }
   for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
      //轮询查找可以支持的解析器
      if (!canLoadFileExtension(propertySourceLoader, extension)) {
         continue;
      }
      NacosByteArrayResource nacosByteArrayResource;
      if (propertySourceLoader instanceof PropertiesPropertySourceLoader) {
         // PropertiesPropertySourceLoader internal is to use the ISO_8859_1,
         // the Chinese will be garbled, needs to transform into unicode.
         nacosByteArrayResource = new NacosByteArrayResource(
               NacosConfigUtils.selectiveConvertUnicode(configValue).getBytes(),
               configName);
      }
      else {
         nacosByteArrayResource = new NacosByteArrayResource(
               configValue.getBytes(), configName);
      }
      nacosByteArrayResource.setFilename(getFileName(configName, extension));
      //解析配置文件
      List<PropertySource<?>> propertySourceList = propertySourceLoader
            .load(configName, nacosByteArrayResource);
      if (CollectionUtils.isEmpty(propertySourceList)) {
         return Collections.emptyList();
      }
      //针对EnumerablePropertySource类型特殊处理一下
      return propertySourceList.stream().filter(Objects::nonNull)
            .map(propertySource -> {
               if (propertySource instanceof EnumerablePropertySource) {
                  String[] propertyNames = ((EnumerablePropertySource) propertySource)
                        .getPropertyNames();
                  if (propertyNames != null && propertyNames.length > 0) {
                     Map<String, Object> map = new LinkedHashMap<>();
                     Arrays.stream(propertyNames).forEach(name -> {
                        map.put(name, propertySource.getProperty(name));
                     });
                     return new OriginTrackedMapPropertySource(
                           propertySource.getName(), map, true);
                  }
               }
               return propertySource;
            }).collect(Collectors.toList());
   }
   return Collections.emptyList();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值