写于: 2019-10-21 22:52:37
一、Config Server 配置的读取与存储
@EnableConfigServer
为入口
@EnableConfigServer
开启了 Spring Cloud Config 功能
@EnableXXX
注解通常使用@Import(xxxConfiguration.class)
的方式引导配置项进行配置 友链:《Springboot 自动化配置#模式一》
@EnableConfigServer
导入的是 ConfigServerConfiguration
,来看看其代码实现
@Configuration
public class ConfigServerConfiguration {
@Bean
public Marker enableConfigServerMarker() {
return new Marker();
}
class Marker {
}
}
代码中的 Bean Marker
是开启 Spring Cloud Config Server 自动配置的开关。
通过在相关配置类上加上
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
实现开关配置。在 《Spring Cloud Zuul【源码篇】揭秘 Zuul》 中提到了 Zuul 开启的注解
@EnableZuulProxy
所导入的配置类ZuulProxyMarkerConfiguration
同样是通过实例化Marker
作为开启条件。
通过 ConfigServerConfiguration.Marker
定位 Config Server 配置类
通过定位,定位到自动配置类
ConfigServerAutoConfiguration
代码如下:
@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class,
ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class,
ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {
}
上述代码中值得关注的两个点
ConfigServerProperties
Config Server 的配置文件
-
导入了 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
@Configuration
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
@Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
return gitEnvironmentRepositoryFactory.build(environmentProperties);
}
}
通过代码能够得知,Spring Cloud Config 默认使用 Git 作为配置文件的管理仓储。
所有仓储的实现默认实现自接口
EnvironmentRepository
,也就是说,如果需要替换仓储实现,只需要实例化相关的实现 Bean,如:JdbcEnvironmentRepository
。同样的,如果需要自定义仓储实现,同样只需要实现接口EnvironmentRepository
即可自定义仓储实现。
默认仓储实现 MultipleJGitEnvironmentRepository
分析
类图结构如下
关注点:
InitializingBean
实现该接口的 Bean 在初始化的时候,会执行
InitializingBean#afterPropertiesSet
方法SearchPathLocator
提供获取配置文件地址的能力
EnvironmentRepository
接口规范:实现
EnvironmentRepository#findOne
,获取环境数据。ResourceLoaderAware
默认是实现:
DefaultResourceLoader
。根据配置文件地址,加载配置文件。
以上几个接口实现,提供了 git 仓储,从初始化、获取git地址、根据git地址加载配置信息、构建 Environment 等一系列能力。
MultipleJGitEnvironmentRepository#findone
-环境配置获取代码
① 相关代码:
public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository {
/**
* @param application 应用名称【spring.application.name 配置】
* @param profile 配置文件环境【如:dev、test。默认:default】
* @param label 分支 【默认:master】
*/
@Override
public Environment findOne(String application, String profile, String label) {
......
return super.findOne(application, profile, label);
......
}
}
聚焦 ② AbstractScmEnvironmentRepository#findOne
代码实现
public abstract class AbstractScmEnvironmentRepository{
@Override
public synchronized Environment findOne(String application, String profile,String label){
NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(
getEnvironment(), new NativeEnvironmentProperties());
// 获取 git 仓库地址
Locations locations = getLocations(application, profile, label);
delegate.setSearchLocations(locations.getLocations());
// 加载配置
Environment result = delegate.findOne(application, profile, "");
result.setVersion(locations.getVersion());
result.setLabel(label);
// 组装数据,并返回
return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
getUri());
}
}
关键逻辑:
- 1、根据 application、profile、label 获取配置文件地址
- 2、加载配置信息
- 3、组装 Environment 数据
这里只关注业务主线,不关注具体实现。
二、Config Server 提供配置读取API
Spring Cloud Config Server 提供了一套完整的 API ,供客户端调用。
相关 API 代码
在上面的提到了 ConfigServerMvcConfiguration
配置类,该配置类中实例化了 EnvironmentController
,该类提供了相关 API 。
EnvironmentController
部分代码如下:
@RestController
@RequestMapping(method = RequestMethod.GET, path = "${spring.cloud.config.server.prefix:}")
public class EnvironmentController {
// 环境仓储:默认 git 实现
private EnvironmentRepository repository;
@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
......
Environment environment = this.repository.findOne(name, profiles, label);
......
return environment;
}
}
EnvironmentRepository
默认实现为:MultipleJGitEnvironmentRepository
测试 API
参考 《Spring-Cloud-Config概述》 中的 config server 服务
在浏览器中,通过:http://{ip}:{port}/{name}/{profiles}/{label} 的方式获取配置信息。
三、扩展:Jdbc 仓储的实现
JDBC 仓储的实现类为:
JdbcEnvironmentRepository
该类实现了EnvironmentRepository
和Ordered
接口。
相关代码实现
聚焦 JdbcEnvironmentRepository#findOne
JDBC 仓储实现
public class JdbcEnvironmentRepository implements EnvironmentRepository, Ordered {
@Override
public Environment findOne(String application, String profile, String label) {
Map<String, String> next = (Map<String, String>) this.jdbc.query(this.sql, new Object[] { app, env, label }, this.extractor);
if (!next.isEmpty()) {
environment.add(new PropertySource(app + "-" + env, next));
}
}
}
代码很直观,简单粗暴,直接通过 SQL
语句获取配置信息。
来看看,其默认的 SQL 语句
SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?
SQL 语句也是十分的清晰直观:直接通过 3个条件 来获取配置信息
- 1、application【应用名称】
- 2、profile【配置环境】
- 3、label【标签,版本】
执行 SQL
获取 key
、value
的键值对。然后组装成 Environment
对象。
四、源码-Client 端如何获取配置信息
简单案例
官方文档介绍 Client 特性:config server 配置信息,填入
bootstrap.properties
中。
bootstrap.properteis 配置如下
# 访问 url
spring.cloud.config.uri = http://localhost:28080
# 配置文件名称
spring.cloud.config.name= config-client
# 配置文件环境
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
代码如下
@Configuration
@EnableConfigurationProperties
public class ConfigServiceBootstrapConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Bean
public ConfigClientProperties configClientProperties() {
ConfigClientProperties client = new ConfigClientProperties(this.environment);
return client;
}
@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(
ConfigClientProperties properties) {
ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
properties);
return locator;
}
}
关注 ConfigServicePropertySourceLocator
,代码如下:
单从字面意思能够猜测该类的功能是 资源定位加载
@Order(0)
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
@Override
@Retryable(interceptor = "configServerRetryInterceptor")
public org.springframework.core.env.PropertySource<?> locate(org.springframework.core.env.Environment environment) {
......
CompositePropertySource composite = new CompositePropertySource("configService");
RestTemplate restTemplate = this.restTemplate == null ? getSecureRestTemplate(properties) : this.restTemplate;
Environment result = getRemoteEnvironment(restTemplate, properties, label.trim(), state);
composite.addFirstPropertySource( new MapPropertySource("configClient", map));
......
return composite;
}
private Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, String label, String state) {
ResponseEntity<Environment> response = null;
// uri = 配置文件中的 spring.cloud.config.uri
// path 拼接的 url:该实例为: /{name}/{profile}
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args);
Environment result = response.getBody();
return result;
}
}
根据案例中给出的配置信息结合代码实现,整理逻辑流程如下:
- 1、拼接 url :http://localhost:28080/{name}/{profile} 其中:name = config-client ,profile = default
- 2、通过 RestTempalte 发起 HTTP 请求,获取的响应数据 Environment
结论:Spring Cloud Config Client 端,通过配置的 URL 地址,拼接 HTTP 请求,以 API 的方式,直接从 Server 端获取配置信息。
五、扩展:客户端如何加载配置中心配置?
通过工具(如:IDEA) 查找 ConfigServicePropertySourceLocator 在那里被调用,整理出调用 UML
③ SpringApplication#applyInitializers
代码如下
public class SpringApplication {
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
}
代码循环遍历所有的 ApplicationContextInitializer
并调用其 initialize
方法。
④ PropertySourceBootstrapConfiguration#initialize
类图如下:
PropertySourceBootstrapConfiguration
是一个 ApplicationContextInitializer
来看看 PropertySourceBootstrapConfiguration#initialize
部分代码实现
@Configuration
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration ......{
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// ConfigServicePropertySourceLocator 配置文件拉取实现类
for (PropertySourceLocator locator : this.propertySourceLocators) {
source = locator.locate(environment);
}
}
}
在 Config client 中完成远程配置拉取的代码在 ConfigServicePropertySourceLocator#locate
。而该类实现了接口 PropertySourceLocator
⑤ ConfigServicePropertySourceLocator#locate
配置文件的拉取在该代码中实现
类图如下
关键代码如下
@Order(0)
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
@Override
@Retryable(interceptor = "configServerRetryInterceptor")
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
// 加载 RestTemplate 请求时需要的配置信息:如 URL ,已经请求参数等。
ConfigClientProperties properties = this.defaultProperties.override(environment);
Environment result = getRemoteEnvironment(restTemplate, properties,
label.trim(), state);
}
private Environment getRemoteEnvironment(RestTemplate restTemplate,
ConfigClientProperties properties, String label, String state) {
// 拼接请求链接
String path = "/{name}/{profile}";
String name = properties.getName();
String profile = properties.getProfile();
......
// HTTP 请求
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,Environment.class, args);
Environment result = response.getBody();
return result;
}
}
上述代码很直观,直接读取配置文件的配置,然后根据配置拼接 url ,发起 HTTP 请求获取到配置。
六、扩展:Client 端配置需要配置在上下文 Bootstrap 中
回顾 ConfigServicePropertySourceLocator#locate
中的一段代码
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
// 加载 RestTemplate 请求时需要的配置信息:如 URL ,已经请求参数等。
ConfigClientProperties properties = this.defaultProperties.override(environment);
......
}
}
上述代码逻辑:获取 client 端的配置信息,这些配置信息是发起对 Server 端 URL 请求的关键信息。
而重点就是配置类 ConfigClientProperties
ConfigClientProperties 配置类
@ConfigurationProperties(ConfigClientProperties.PREFIX)
public class ConfigClientProperties {
public static final String PREFIX = "spring.cloud.config";
private String profile = "default";
private String label;
private String[] uri = { "http://localhost:8888" };
@Value("${spring.application.name:application}")
private String name;
......
}
该配置主要加载的是:spring.cloud.config 为前缀的配置信息。
ConfigServiceBootstrapConfiguration 加载 ConfigClientProperties 配置类
代码如下:
@Configuration
@EnableConfigurationProperties
public class ConfigServiceBootstrapConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Bean
public ConfigClientProperties configClientProperties() {
// 设置 profile 值
ConfigClientProperties client = new ConfigClientProperties(this.environment);
return client;
}
@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(
ConfigClientProperties properties) {
ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
properties);
return locator;
}
}
由于配置加载优先级的原因,此时 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。