『Naocs 2x』(七) Nacos 配置中心是如何与

前言

之前写了部分 Nacos 注册中心的源码分析文章,现在继续接着看 Nacos 配置中心的源码了。
本次目标有两个:

  • 了解 Spring Boot 项目启动时,如何从 Nacos 配置中心获取初始化配置。
  • Nacos 配置中心配置变更时,Spring Boot 项目如何更新。

当然实践至上,当弄明白上述两个问题后,也需要写一个丐版注册中心来加强认知。

丐版注册中心需要实现两个功能:

  • Spring Boot 项目启动时,可从丐版注册中心获取初始配置。
  • 丐版注册中心配置变更时,Spring Boot 项目能更新配置。

Spring Boot 初始化器

我们在《 『深入学习 Spring Boot』(二) 系统初始化器 》中曾学习到:

官方描述:系统初始化器是Spring容器刷新之前执行的一个回调函数。

作用是向Spring Boot容器中注册属性,需要实现ApplicationContextInitializer接口。

Spring Cloud 配置中心就是依靠ApplicationContextInitializer去调用远程接口,获取配置信息。

org.springframework.cloud.bootstrap.config包下,有此类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
ini复制代码public class PropertySourceBootstrapConfiguration implements
       ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

 @Autowired(required = false)
   private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();

  // ......
 @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) {
     // 执行 locateCollection 方法,获取具体配置。
     // 注意本方法执行后,配置已经被从远程加载进来了。
           Collection<PropertySource<?>> source = locator.locateCollection(environment);
           // ....
           List<PropertySource<?>> sourceList = new ArrayList<>();
           for (PropertySource<?> p : source) {
               if (p instanceof EnumerablePropertySource) {
                   EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
                   sourceList.add(new BootstrapPropertySource<>(enumerable));
              }
               // ....
          }
           composite.addAll(sourceList);
           empty = false;
      }
       if (!empty) {
           MutablePropertySources propertySources = environment.getPropertySources();
           // ....
     // 把 composite(远程的属性) 设置到 环境类中
           insertPropertySources(propertySources, composite);
           // ....
      }
  }

 // .....
}

这里还涉及到了,Spring 上下文中的Environment类。

如果觉得陌生,可先阅读 《『深入学习 Spring Boot』(十) Environment 》、《『深入学习 Spring Boot』(十一) profiles 》我的两篇博客。

此类就是 Spring Boot 与 Nacos 之间的 God Class了。

PropertySourceLocator 属性加载器

这是一个接口定义,其实现类将真正读取属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scss复制代码public interface PropertySourceLocator {

   PropertySource<?> locate(Environment environment);

   default Collection<PropertySource<?>> locateCollection(Environment environment) {
       return locateCollection(this, environment);
  }
 // 注意,上文 PropertySourceBootstrapConfiguration # initialize,调用了此方法。
 // 也就是调用了 locate 方法。
   static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
           Environment environment) {
       PropertySource<?> propertySource = locator.locate(environment);
   // ....
  }

}

查看此接口的关系:

image-20211107225427546

好巧不巧,有个 Nacos 相关的实现。这下子可以串起来了。

Spring Boot 项目启动时,将执行 PropertySourceBootstrapConfiguration # initialize(),在其中获取到 PropertySourceLocator的实现类遍历执行locate()方法。

那么我们可以有一个猜想了,NacosPropertySourceLocatorlocate()中,请求配置中心获取到配置,设置到Environment中,Spring Boot 不就可以正常启动了。

NacosPropertySourceLocator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
arduino复制代码public class NacosPropertySourceLocator implements PropertySourceLocator {

 @Override
   public PropertySource<?> locate(Environment env) {
       // ....

       CompositePropertySource composite = new CompositePropertySource(
               NACOS_PROPERTY_SOURCE_NAME);

   // 1.加载共享配置
       loadSharedConfiguration(composite);
   // 2.加载扩展配置
       loadExtConfiguration(composite);
   // 3.加载应用配置
       loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

       return composite;
  }

 // 1、2、3底层都是通过调用 loadNacosDataIfPresent
   private void loadNacosDataIfPresent(final CompositePropertySource composite,
       final String dataId, final String group, String fileExtension,
       boolean isRefreshable) {
   // ....
   NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
           fileExtension, isRefreshable);
   this.addFirstPropertySource(composite, propertySource, false);
}

 // 继续调用 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);
          }
      }
   // 这里是发请求的方法
       return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
               isRefreshable);
  }
}

NacosPropertySourceBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typescript复制代码public class NacosPropertySourceBuilder {

   NacosPropertySource build(String dataId, String group, String fileExtension,
           boolean isRefreshable) {
       Map<String, Object> p = loadNacosData(dataId, group, fileExtension);
       NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
               p, new Date(), isRefreshable);
       NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
       return nacosPropertySource;
  }

   private Map<String, Object> loadNacosData(String dataId, String group,
           String fileExtension) {
       String data = null;
       try {
     // 获取配置。重点。
           data = configService.getConfig(dataId, group, timeout);
           // 格式化配置
           Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
                  .parseNacosData(data, fileExtension);
           return dataMap == null ? EMPTY_MAP : dataMap;
      }
       return EMPTY_MAP;
  }
}

NacosConfigService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
vbnet复制代码public class NacosConfigService implements ConfigService {

   @Override
   public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
       return getConfigInner(namespace, dataId, group, timeoutMs);
  }

 private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
       group = blank2defaultGroup(group);
       ParamUtils.checkKeyParam(dataId, group);
       ConfigResponse cr = new ConfigResponse();
       cr.setDataId(dataId);
       cr.setTenant(tenant);
       cr.setGroup(group);

           // ...

       try {
           // 从远程获取配置信息。content 字符串的内容就是具体的配置内容。
           ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
           cr.setContent(response.getContent());
           cr.setEncryptedDataKey(response.getEncryptedDataKey());
           configFilterChainManager.doFilter(null, cr);
           content = cr.getContent();
           return content;
      } catch (NacosException ioe) {
           // ...
      }
       // ...
  }
}

image-20211108224422948

从远程获取到配置项后,就可以保存到上下文环境中,进行应用程序的启动了。

小结

Nacos 配置中心是如何与 Spring Cloud 结合的?

  1. Spring Boot 有初始化器机制,专门用于初始化属性的。Spring Cloud 实现了一个初始化器,并在其中调用PropertySourceLocator#locate()
  2. Nacos 有一PropertySourceLocator的实现类,并在locate()方法中进行远程调用,获取具体配置项。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%