本文主要讨论原理,不涉及使用示例。
一 搭建Config Server
SpringCloud Config支持通过git、svn等搭建配置中心。因为目前使用git管理代码比较常见,所以接下来介绍通过git搭建配置中心。
1 配置示例
以下git相关的是一些常用的配置:
spring.cloud.config.server.git.uri= Git仓库地址
spring.cloud.config.server.git.searchPaths=仓库中配置文件所在路径
spring.cloud.config.server.git.username= Git账号
spring.cloud.config.server.git.password= Git密码
配置后,可以通过一下url模式访问指定的配置文件的内容,例如:
http://localhost:7001/{application}/{profile}[/{label}]
http://localhost:7001/{application}-{profile}.yml
http://localhost:7001/{label}/{application}-{profile}.yml
http://localhost:7001/{application}-{profile}.properties
http://localhost:7001/{label}/{application}-{profile}.properties
2 整体结构
远程git仓库:用来存储配置文件的地方,多环境配置文件使用 {application}-{profile}.yml、{application}-{profile}.propreties
本地git仓库:缓存从远程git仓库中获取的配置信息,即便是远程git不能用,也可以从本地仓库中加载配置内容。
Config Server:分布式配置中心,指定了git仓库uri、搜索路径、访问账号和密码等。
微服务应用:配置客户端(Config Client),指定应用名、配置中心url、环境名、分支名等。
3 启动流程
微服务应用启动,根据微服务系统中配置的应用名(application)、环境名(profile)、分支名(label),向Config Server请求配置信息。
Config Server根据配置的Git(或SVN)仓库信息加上客户端传来的配置定位信息去查配置信息的路径。
Config Server执行git clone命令,将配置信息下载到本地Git仓库中,将配置信息加载到Spring的ApplicationContext读取内容返回给客户端(微服务应用)
客户端将内容加载到ApplicationContext,配置内容的优先级大于客户端内部的配置内容,进行忽略
注意:Config Server从Git上拉取配置项以后,会缓存在本地。
4 源码分析
1) EnvironmentRepository
在前面「整体结构」部分中,我们看到ConfigServer从仓库中获取配置信息,然后给到其他微服务,这里ConfigServer与仓库的操作都是通过EnvironmentRepository来实现的。EnvironmentRepository有一下几种:
NativeEnvironmentRepository |
spring.profiles.active=native时使用,从应用的配置文件中加载 |
EnvironmentEncryptorEnvironmentRepository |
加密场景 |
PassthruEnvironmentRepository |
Native时其实使用的是这个类来处理的 |
SvnKitEnvironmentRepository |
通过svn来管理配置项时 |
JGitEnvironmentRepository |
通过git来管理配置项时。 |
参考EnvironmentRepositoryConfiguration类中的代码,如下所示:
@Configuration
@Profile("native")
protected static class NativeRepositoryConfiguration {
//代码略去
@Bean
public EnvironmentRepository environmentRepository() {
return new NativeEnvironmentRepository(this.environment);
}
}
@Configuration
@ConditionalOnMissingBean(EnvironmentRepository.class)
protected static class GitRepositoryConfiguration {
//代码略去
@Bean
public EnvironmentRepository environmentRepository() {
MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
@Configuration
@Profile("subversion")
protected static class SvnRepositoryConfiguration {
//代码略去
@Bean
public EnvironmentRepository environmentRepository() {
SvnKitEnvironmentRepository repository = new SvnKitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel()!=null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
上面的所有类都实现了EnvironmentRepository接口,这个接口之定义了一个方法,用来从Repository中查找需要的数据项。
public interface EnvironmentRepository {
Environment findOne(String application, String profile, String label);
}
2) 以git为例,简单介绍仓库代码管理过程
服务器启动时,属性设置完以后就会根据cloneOnStart的值决定是否从远程git仓库clone代码,如下JGitEnvironmentRepository#afterPropertiesSet方法所示:
public void afterPropertiesSet() throws Exception {
Assert.state(getUri() != null,
"You need to configure a uri for the git repository");
if (this.cloneOnStart) {
initClonedRepository();
}
}
/**
* Clones the remote repository and then opens a connection to it.
* @throws GitAPIException
* @throws IOException
*/
private void initClonedRepository() throws GitAPIException, IOException {
if (!getUri().startsWith(FILE_URI_PREFIX)) {
deleteBaseDirIfExists();
Git git = cloneToBasedir();
if (git != null) {
git.close();
}
git = openGitRepository();
if (git != null) {
git.close();
}
}
}
从源码中我们看到,首先会将配置从git上clone到本地,然后再进行其他的操作。接着就本地的git仓库中获取指定的数据了,见AbstractScmEnvironmentRepository#findOne代码,如下所示:
@Override
public synchronized Environment findOne(String application, String profile, String label) {
NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(
getEnvironment());
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());
}
注意代码中使用的是NativeEnvironmentRepository,这个是以本地的文件作为EnvironmentRepository的。
二 搭建Client
1 配置说明
在大家server部分我们提到了application、profile、label,他们分别是通过一下配置项配置的。
spring.application.name:对应配置文件规则中的{application}部分
spring.cloud.config.profile:对应配置文件规则中的{profile}部分
spring.cloud.config.label:对应配置文件规则中的{label}部分
spring.cloud.config.uri:配置中心config-server的地址
示例如下:
spring.application.name=demo
spring.cloud.config.profile=dev
spring.cloud.config.label=master
那么可以通过以下url访问:http://localhost:7001/demo/dev
2 源码分析
ConfigClientProperties#override方法中,我们可以看到通过客户端配置的spring.application.name、spring.cloud.config.profile、spring.cloud.config.label、spring.cloud.config.uri来生成ConfigClientProperties对象,而ConfigClientProperties中包含了要获取那个应用、分支、环境的配置信息。如下所示:
public static final String ConfigClientProperties
.PREFIX = "spring.cloud.config"
public ConfigClientProperties override(
org.springframework.core.env.Environment environment) {
ConfigClientProperties override = new ConfigClientProperties();
BeanUtils.copyProperties(this, override);
override.setName(environment.resolvePlaceholders("${" + ConfigClientProperties.PREFIX + ".name:${spring.application.name:application}}"));
if (environment.containsProperty(ConfigClientProperties.PREFIX + ".profile")) {
override.setProfile(environment.getProperty(ConfigClientProperties.PREFIX + ".profile"));
}
if (environment.containsProperty(ConfigClientProperties.PREFIX + ".label")) {
override.setLabel(
environment.getProperty(ConfigClientProperties.PREFIX + ".label"));
}
return override;
}