写于: 2019-10-21 22:52:37

参考资料: Spring Cloud Config 官网 Spring Cloud Config GItub

Spring Cloud Config 概述

一、Config Server 配置的读取与存储

@EnableConfigServer 为入口

@EnableConfigServer 开启了 Spring Cloud Config 功能

@EnableXXX 注解通常使用 @Import(xxxConfiguration.class) 的方式引导配置项进行配置 友链:《Springboot 自动化配置#模式一》

@EnableConfigServer 导入的是 ConfigServerConfiguration,来看看其代码实现

  1. @Configuration
  2. public class ConfigServerConfiguration {
  3. @Bean
  4. public Marker enableConfigServerMarker() {
  5. return new Marker();
  6. }
  7. class Marker {
  8. }
  9. }

代码中的 Bean Marker 是开启 Spring Cloud Config Server 自动配置的开关。

通过在相关配置类上加上 @ConditionalOnBean(ConfigServerConfiguration.Marker.class)实现开关配置。

《Spring Cloud Zuul【源码篇】揭秘 Zuul》 中提到了 Zuul 开启的注解 @EnableZuulProxy 所导入的配置类ZuulProxyMarkerConfiguration 同样是通过实例化 Marker 作为开启条件。

通过 ConfigServerConfiguration.Marker 定位 Config Server 配置类

通过定位,定位到自动配置类 ConfigServerAutoConfiguration

代码如下:

  1. @Configuration
  2. @ConditionalOnBean(ConfigServerConfiguration.Marker.class)
  3. @EnableConfigurationProperties(ConfigServerProperties.class)
  4. @Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class,
  5. ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class,
  6. ConfigServerMvcConfiguration.class })
  7. public class ConfigServerAutoConfiguration {
  8. }

上述代码中值得关注的两个点

  • ConfigServerProperties

    Config Server 的配置文件

  • @Import(xxx)

    导入了 Config Server 的多种配置信息 EnvironmentRepositoryConfiguration 仓储配置,包括:JDBC、GIT、SVN 等多种配置仓储实现配置。 CompositeConfiguration 多环境仓储配置。EnvironmentRepositoryConfiguration 中如果配置了多种仓储,CompositeConfiguration 会整合生成 SearchPathLocator ResourceRepositoryConfiguration 文件资源仓库配置。 ConfigServerEncryptionConfiguration 加解密功能配置。 ConfigServerMvcConfiguration API 开放查询配置接口配置。如:EnvironmentController 仓储配置查询 API。

EnvironmentRepositoryConfiguration.class 仓储配置

EnvironmentRepositoryConfiguration 中有关于:JDBC、GIT、SVN 等仓储配置,为了节省篇幅,直接来看默认实现仓储,代码如下

在多种配置仓储实现中,Spring Cloud 提供了默认的仓储实现:MultipleJGitEnvironmentRepository,对应的自动配置类为:DefaultRepositoryConfiguration

  1. @Configuration
  2. @ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
  3. class DefaultRepositoryConfiguration {
  4. @Bean
  5. public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
  6. MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
  7. MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
  8. return gitEnvironmentRepositoryFactory.build(environmentProperties);
  9. }
  10. }

通过代码能够得知,Spring Cloud Config 默认使用 Git 作为配置文件的管理仓储。

所有仓储的实现默认实现自接口 EnvironmentRepository ,也就是说,如果需要替换仓储实现,只需要实例化相关的实现 Bean,如:JdbcEnvironmentRepository 。同样的,如果需要自定义仓储实现,同样只需要实现接口 EnvironmentRepository 即可自定义仓储实现。

默认仓储实现 MultipleJGitEnvironmentRepository 分析

类图结构如下
01.png
关注点:

  • InitializingBean

    实现该接口的 Bean 在初始化的时候,会执行 InitializingBean#afterPropertiesSet 方法

  • SearchPathLocator

    提供获取配置文件地址的能力

  • EnvironmentRepository

    接口规范:实现 EnvironmentRepository#findOne ,获取环境数据。

  • ResourceLoaderAware

    默认是实现:DefaultResourceLoader 。根据配置文件地址,加载配置文件。

以上几个接口实现,提供了 git 仓储,从初始化、获取git地址、根据git地址加载配置信息、构建 Environment 等一系列能力。

MultipleJGitEnvironmentRepository#findone-环境配置获取代码

03.png

① 相关代码:

  1. public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository {
  2. /**
  3. * @param application 应用名称【spring.application.name 配置】
  4. * @param profile 配置文件环境【如:dev、test。默认:default】
  5. * @param label 分支 【默认:master】
  6. */
  7. @Override
  8. public Environment findOne(String application, String profile, String label) {
  9. ......
  10. return super.findOne(application, profile, label);
  11. ......
  12. }
  13. }

聚焦 ② AbstractScmEnvironmentRepository#findOne 代码实现

  1. public abstract class AbstractScmEnvironmentRepository{
  2. @Override
  3. public synchronized Environment findOne(String application, String profile,String label){
  4. NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(
  5. getEnvironment(), new NativeEnvironmentProperties());
  6. // 获取 git 仓库地址
  7. Locations locations = getLocations(application, profile, label);
  8. delegate.setSearchLocations(locations.getLocations());
  9. // 加载配置
  10. Environment result = delegate.findOne(application, profile, "");
  11. result.setVersion(locations.getVersion());
  12. result.setLabel(label);
  13. // 组装数据,并返回
  14. return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
  15. getUri());
  16. }
  17. }

关键逻辑:

  • 1、根据 application、profile、label 获取配置文件地址
  • 2、加载配置信息
  • 3、组装 Environment 数据

这里只关注业务主线,不关注具体实现。

二、Config Server 提供配置读取API

Spring Cloud Config Server 提供了一套完整的 API ,供客户端调用。

相关 API 代码

在上面的提到了 ConfigServerMvcConfiguration 配置类,该配置类中实例化了 EnvironmentController ,该类提供了相关 API 。

EnvironmentController 部分代码如下:

  1. @RestController
  2. @RequestMapping(method = RequestMethod.GET, path = "${spring.cloud.config.server.prefix:}")
  3. public class EnvironmentController {
  4. // 环境仓储:默认 git 实现
  5. private EnvironmentRepository repository;
  6. @RequestMapping("/{name}/{profiles}/{label:.*}")
  7. public Environment labelled(@PathVariable String name, @PathVariable String profiles,
  8. @PathVariable String label) {
  9. ......
  10. Environment environment = this.repository.findOne(name, profiles, label);
  11. ......
  12. return environment;
  13. }
  14. }

EnvironmentRepository默认实现为:MultipleJGitEnvironmentRepository

测试 API

参考 《Spring-Cloud-Config概述》 中的 config server 服务

在浏览器中,通过:http://{ip}:{port}/{name}/{profiles}/{label} 的方式获取配置信息。
02.png

三、扩展:Jdbc 仓储的实现

JDBC 仓储的实现类为:JdbcEnvironmentRepository 该类实现了 EnvironmentRepository 和 Ordered 接口。

相关代码实现

聚焦 JdbcEnvironmentRepository#findOne JDBC 仓储实现

  1. public class JdbcEnvironmentRepository implements EnvironmentRepository, Ordered {
  2. @Override
  3. public Environment findOne(String application, String profile, String label) {
  4. Map<String, String> next = (Map<String, String>) this.jdbc.query(this.sql, new Object[] { app, env, label }, this.extractor);
  5. if (!next.isEmpty()) {
  6. environment.add(new PropertySource(app + "-" + env, next));
  7. }
  8. }
  9. }

代码很直观,简单粗暴,直接通过 SQL 语句获取配置信息。

来看看,其默认的 SQL 语句

SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?

SQL 语句也是十分的清晰直观:直接通过 3个条件 来获取配置信息

  • 1、application【应用名称】
  • 2、profile【配置环境】
  • 3、label【标签,版本】

执行 SQL 获取 keyvalue 的键值对。然后组装成 Environment 对象。

四、源码-Client 端如何获取配置信息

简单案例

官方文档介绍 Client 特性:config server 配置信息,填入 bootstrap.properties 中。

bootstrap.properteis 配置如下

  1. # 访问 url
  2. spring.cloud.config.uri = http://localhost:28080
  3. # 配置文件名称
  4. spring.cloud.config.name= config-client
  5. # 配置文件环境
  6. spring.cloud.config.profile = dev

在引入 spring-cloud-starter-config client 的依赖之后,添加上面的两个配置,即可完成配置文件的读取。

在上面提到了的 EnvironmentController 提供的某个API 为 http://{ip}:{port}/{name}/{profiles}。对比配置文件中的 uri,name,profile 就能够猜测得到 client 获取配置的方式

源码查看client具体操作

以 client 端的自动配置类 ConfigServiceBootstrapConfiguration 为入口进行分析

Config Client 自动配置类 ConfigServiceBootstrapConfiguration 代码如下

  1. @Configuration
  2. @EnableConfigurationProperties
  3. public class ConfigServiceBootstrapConfiguration {
  4. @Autowired
  5. private ConfigurableEnvironment environment;
  6. @Bean
  7. public ConfigClientProperties configClientProperties() {
  8. ConfigClientProperties client = new ConfigClientProperties(this.environment);
  9. return client;
  10. }
  11. @Bean
  12. @ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
  13. @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
  14. public ConfigServicePropertySourceLocator configServicePropertySource(
  15. ConfigClientProperties properties) {
  16. ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
  17. properties);
  18. return locator;
  19. }
  20. }

关注 ConfigServicePropertySourceLocator,代码如下:

单从字面意思能够猜测该类的功能是 资源定位加载

  1. @Order(0)
  2. public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
  3. @Override
  4. @Retryable(interceptor = "configServerRetryInterceptor")
  5. public org.springframework.core.env.PropertySource<?> locate(org.springframework.core.env.Environment environment) {
  6. ......
  7. CompositePropertySource composite = new CompositePropertySource("configService");
  8. RestTemplate restTemplate = this.restTemplate == null ? getSecureRestTemplate(properties) : this.restTemplate;
  9. Environment result = getRemoteEnvironment(restTemplate, properties, label.trim(), state);
  10. composite.addFirstPropertySource( new MapPropertySource("configClient", map));
  11. ......
  12. return composite;
  13. }
  14. private Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, String label, String state) {
  15. ResponseEntity<Environment> response = null;
  16. // uri = 配置文件中的 spring.cloud.config.uri
  17. // path 拼接的 url:该实例为: /{name}/{profile}
  18. response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args);
  19. Environment result = response.getBody();
  20. return result;
  21. }
  22. }

根据案例中给出的配置信息结合代码实现,整理逻辑流程如下:

结论:Spring Cloud Config Client 端,通过配置的 URL 地址,拼接 HTTP 请求,以 API 的方式,直接从 Server 端获取配置信息。

五、扩展:客户端如何加载配置中心配置?

友链:《Spring Boot run 直接启动应用》

通过工具(如:IDEA) 查找 ConfigServicePropertySourceLocator 在那里被调用,整理出调用 UML
04.png

③ SpringApplication#applyInitializers

代码如下

  1. public class SpringApplication {
  2. protected void applyInitializers(ConfigurableApplicationContext context) {
  3. for (ApplicationContextInitializer initializer : getInitializers()) {
  4. Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
  5. initializer.getClass(), ApplicationContextInitializer.class);
  6. Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
  7. initializer.initialize(context);
  8. }
  9. }
  10. }

代码循环遍历所有的 ApplicationContextInitializer 并调用其 initialize 方法。

④ PropertySourceBootstrapConfiguration#initialize

类图如下:
05.png

PropertySourceBootstrapConfiguration 是一个 ApplicationContextInitializer

来看看 PropertySourceBootstrapConfiguration#initialize 部分代码实现

  1. @Configuration
  2. @EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
  3. public class PropertySourceBootstrapConfiguration ......{
  4. @Override
  5. public void initialize(ConfigurableApplicationContext applicationContext) {
  6. // ConfigServicePropertySourceLocator 配置文件拉取实现类
  7. for (PropertySourceLocator locator : this.propertySourceLocators) {
  8. source = locator.locate(environment);
  9. }
  10. }
  11. }

在 Config client 中完成远程配置拉取的代码在 ConfigServicePropertySourceLocator#locate 。而该类实现了接口 PropertySourceLocator

⑤ ConfigServicePropertySourceLocator#locate

配置文件的拉取在该代码中实现

类图如下
06.png
关键代码如下

  1. @Order(0)
  2. public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
  3. @Override
  4. @Retryable(interceptor = "configServerRetryInterceptor")
  5. public org.springframework.core.env.PropertySource<?> locate(
  6. org.springframework.core.env.Environment environment) {
  7. // 加载 RestTemplate 请求时需要的配置信息:如 URL ,已经请求参数等。
  8. ConfigClientProperties properties = this.defaultProperties.override(environment);
  9. Environment result = getRemoteEnvironment(restTemplate, properties,
  10. label.trim(), state);
  11. }
  12. private Environment getRemoteEnvironment(RestTemplate restTemplate,
  13. ConfigClientProperties properties, String label, String state) {
  14. // 拼接请求链接
  15. String path = "/{name}/{profile}";
  16. String name = properties.getName();
  17. String profile = properties.getProfile();
  18. ......
  19. // HTTP 请求
  20. response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,Environment.class, args);
  21. Environment result = response.getBody();
  22. return result;
  23. }
  24. }

上述代码很直观,直接读取配置文件的配置,然后根据配置拼接 url ,发起 HTTP 请求获取到配置。

六、扩展:Client 端配置需要配置在上下文 Bootstrap 中

回顾 ConfigServicePropertySourceLocator#locate 中的一段代码

  1. public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
  2. public org.springframework.core.env.PropertySource<?> locate(
  3. org.springframework.core.env.Environment environment) {
  4. // 加载 RestTemplate 请求时需要的配置信息:如 URL ,已经请求参数等。
  5. ConfigClientProperties properties = this.defaultProperties.override(environment);
  6. ......
  7. }
  8. }

上述代码逻辑:获取 client 端的配置信息,这些配置信息是发起对 Server 端 URL 请求的关键信息。

而重点就是配置类 ConfigClientProperties

ConfigClientProperties 配置类

  1. @ConfigurationProperties(ConfigClientProperties.PREFIX)
  2. public class ConfigClientProperties {
  3. public static final String PREFIX = "spring.cloud.config";
  4. private String profile = "default";
  5. private String label;
  6. private String[] uri = { "http://localhost:8888" };
  7. @Value("${spring.application.name:application}")
  8. private String name;
  9. ......
  10. }

该配置主要加载的是:spring.cloud.config 为前缀的配置信息。

ConfigServiceBootstrapConfiguration 加载 ConfigClientProperties 配置类

代码如下:

  1. @Configuration
  2. @EnableConfigurationProperties
  3. public class ConfigServiceBootstrapConfiguration {
  4. @Autowired
  5. private ConfigurableEnvironment environment;
  6. @Bean
  7. public ConfigClientProperties configClientProperties() {
  8. // 设置 profile 值
  9. ConfigClientProperties client = new ConfigClientProperties(this.environment);
  10. return client;
  11. }
  12. @Bean
  13. @ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
  14. @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
  15. public ConfigServicePropertySourceLocator configServicePropertySource(
  16. ConfigClientProperties properties) {
  17. ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
  18. properties);
  19. return locator;
  20. }
  21. }

由于配置加载优先级的原因,此时 ConfigurableEnvironment 中只加载了 bootstrap.properties 中的配置信息,而 application.properties 的配置信息未加载。换言之,如果此时 spring cloud client 的相关配置配置在了application.properteis 中,便无法读取到相关的配置信息,所有 spring cloud client 的相关配置需要配置在 bootstrap.properteis 。

七、总结

Spring Cloud Config Server 端

提供多种配置仓储实现:SVN、Git、JDBC、zk等。默认:Git 仓储实现。
同时通过 EnvironmentController 对外提供一整套完整的 API供客户端调用。

Spring Cloud Config Client 端

通过 Spring Boot 启动运行过程中,context 上下文初始化的过程中,根据相关的配置,从 Server 端拉取配置信息 Environment