说明:本文使用的源码基于Spring Cloud Config Server 3.0.5版本
在 Spring Cloud Config 中,针对服务器端和客户端组件分别构建了 spring-cloud-config-server 和 spring-cloud-config-client 这两个代码工程。我们今天主要来看Spring Cloud Config Server的工作机制。
Spring Cloud Config Server工作机制
- Spring Cloud Config Server配置加载过程
首先从@EnableConfigServer说起
@EnableConfigServer注解的源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {
}
这里通过 @Import 注解引入了 ConfigServerConfiguration类该类中定义了一个 Marker 空类。在 Spring Boot 的自动配置体系中,这是常见的一种处理方式。这个 Marker 类的作用就是为了提供一个启动条件,而这个启动条件的唯一使用者就是 ConfigServerAutoConfiguration,如下所示:
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@ConditionalOnProperty(name = ConfigServerProperties.PREFIX + ".enabled", matchIfMissing = true)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class,
ResourceEncryptorConfiguration.class })
public class ConfigServerAutoConfiguration {
}
这里的 @ConditionalOnBean(ConfigServerConfiguration.Marker.class) 条件注解表示只有在类路径中构建了这个 Marker 类的实例时才会执行 ConfigServerAutoConfiguration 的处理。同时,这里又进一步导入了一批配置类,我们重点关注EnvironmentRepositoryConfiguration类,对于 Spring Cloud Config 而言,它把所有的配置信息抽象为一种 Environment(环境),而存储这些配置信息的地方就称为 EnvironmentRepository。EnvironmentRepository 就是带有配置仓库的配置中心实现方案的具体体现,它是一个接口,定义如下:
public interface EnvironmentRepository {
Environment findOne(String application, String profile, String label);
default Environment findOne(String application, String profile, String label, boolean includeOrigin) {
return findOne(application, profile, label);
}
}
Spring Cloud Config中把配置信息抽象为应用(application)、环境(profile)和版本(label)这三个维度进行管理,通过这三个维度,我们就可以确定唯一的一份配置数据。EnvironmentRepository 的实现类非常多,参见下图,从命名中基本都可以看出这些类是用于加载哪些不同类型的配置:

从EnvironmentRepository 的实现来看,目前版本的实现已经有基于Git、Svn、Jdbc、Reids等第三方工具作为配置中心的实现。在EnvironmentRepositoryConfiguration类中我们看到了许多EnvironmentRepository的加载配置,其中默认的配置为DefaultRepositoryConfiguration。代码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
@Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
return gitEnvironmentRepositoryFactory.build(environmentProperties);
}
}
而GitRepositoryConfiguration继承了DefaultRepositoryConfiguration
@Configuration(proxyBeanMethods = false)
@Profile("git")
class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {
}
也就是说 Spring Cloud Config 中默认使用 Git 作为配置仓库来完成配置信息的存储和管理,提供的 EnvironmentRepository 就是 MultipleJGitEnvironmentRepository,而 MultipleJGitEnvironmentRepository 则继承了抽象类 JGitEnvironmentRepository。
当服务器启动时,在 JGitEnvironmentRepository 中会决定是否调用 initClonedRepository() 方法来完成从远程 Git 仓库 Clone 代码。如果执行了这一操作,相当于会将配置文件从 Git 上 clone 到本地,然后再进行其他的操作。在 JGitEnvironmentRepository 抽象类中,提供了大量针对第三方 Git 仓库的操作代码,无论采用诸如 Git、SVN 等具体某一种配置仓库的实现方式,最终我们处理的对象都是位于本地文件系统中的配置文件。为了理解这点,我们需要围绕 MultipleJGitEnvironmentRepository 类从下向上回顾整个类层结构,如下图所示:

上图中,AbstractScmEnvironmentRepository 实现了 EnvironmentRepository 接口,同时也是 JGitEnvironmentRepository 的父类,它的 findOne 方法如下所示:
@Override
public synchronized Environment findOne(String application, String profile, String label, boolean includeOrigin) {
NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(getEnvironment(),
new NativeEnvironmentProperties());
Locations locations = getLocations(application, profile, label);
delegate.setSearchLocations(locations.getLocations());
Environment result = delegate.findOne(application, profile, "", includeOrigin);
result.setVersion(locations.getVersion());
result.setLabel(label);
return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(), getUri());
}
我们看到AbstractScmEnvironmentRepository 的findOne方法调用了NativeEnvironmentRepository的findOne方法
@Override
public Environment findOne(String config, String profile, String label, boolean includeOrigin) {
try {
ConfigurableEnvironment environment = getEnvironment(config, profile, label);
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Map<org.springframework.core.env.PropertySource<?>, PropertySourceConfigData> propertySourceToConfigData = new HashMap<>();
ConfigDataEnvironmentPostProcessor.applyTo(environment, resourceLoader, null,
StringUtils.commaDelimitedListToSet(profile), new ConfigDataEnvironmentUpdateListener() {
@Override
public void onPropertySourceAdded(org.springframework.core.env.PropertySource<?> propertySource,
ConfigDataLocation location, ConfigDataResource resource) {
propertySourceToConfigData.put(propertySource,
new PropertySourceConfigData(location, resource));
}
});
environment.getPropertySources().remove("config-data-setup");
return clean(new PassthruEnvironmentRepository(environment).findOne(config, profile, label, includeOrigin),
propertySourceToConfigData);
}
catch (Exception e) {
String msg = String.format("Could not construct context for config=%s profile=%s label=%s includeOrigin=%b",
config, profile, label, includeOrigin);
String completeMessage = NestedExceptionUtils.buildMessage(msg,
NestedExceptionUtils.getMostSpecificCause(e));
throw new FailedToConstructEnvironmentException(completeMessage, e);
}
}
从代码结构上,我们看到最终委托 PassthruEnvironmentRepository 完成配置文件的读取,然后通过 clean 方法完成本地文件地址与远程仓库之间地址的转换。ConfigDataEnvironmentUpdateListener用于监听Environment的更新。
2. 将配置暴漏给用户
在 Spring Cloud Config 中,通过 EnvironmentRepository 获取的配置信息最终通过 EnvironmentController 暴露给客户端应用程序进行。EnvironmentController 类比较简单,类的定义如下所示:
@RestController
@RequestMapping(method = RequestMethod.GET, path = "${spring.cloud.config.server.prefix:}")
public class EnvironmentController {
private EnvironmentRepository repository;
private ObjectMapper objectMapper;
......
}
EnvironmentController关键的成员变量只有两个,即 EnvironmentRepository 和 ObjectMapper。前者是具体某一个 EnvironmentRepository 的实例,而 ObjectMapper 用于当将结果序列化成 JSON 格式的配置数据。
EnvironmentController 提供了多种获取配置信息的方法,这些方法接收前面介绍的 application、profile、label 这三个参数。EnvironmentController 中最重要的方法就是如下所示的 defaultLabel 方法和 labelled 方法,这些方法暴露了最常用的获取配置的 HTTP 端点:
@RequestMapping(path = "/{name}/{profiles:.*[^-].*}", produces = MediaType.APPLICATION_JSON_VALUE)
public Environment defaultLabel(@PathVariable String name, @PathVariable String profiles) {
return getEnvironment(name, profiles, null, false);
}
@RequestMapping(path = "/{name}/{profiles:.*[^-].*}", produces = EnvironmentMediaType.V2_JSON)
public Environment defaultLabelIncludeOrigin(@PathVariable String name, @PathVariable String profiles) {
return getEnvironment(name, profiles, null, true);
}
@RequestMapping(path = "/{name}/{profiles}/{label:.*}", produces = MediaType.APPLICATION_JSON_VALUE)
public Environment labelled(@PathVariable String name, @PathVariable String profiles, @PathVariable String label) {
return getEnvironment(name, profiles, label, false);
}
@RequestMapping(path = "/{name}/{profiles}/{label:.*}", produces = EnvironmentMediaType.V2_JSON)
public Environment labelledIncludeOrigin(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
return getEnvironment(name, profiles, label, true);
}
以上就是Spring Cloud Config 来完成配置中心服务器端的构建过程。
2737



被折叠的 条评论
为什么被折叠?



