说明:本版本的Spring Cloud Config Client的版本为3.0.5
Spring Cloud Config Client实现的依赖关系图大致如下:
接下来我们一同探讨Spring Cloud Config Client的实现原理
- ConfigServiceBootstrapConfiguration类的自动装配
在spring-cloud-config-client-3.0.5.jar包中的meta-INF的spring.factories文件中包含
# Bootstrap components org.springframework.cloud.bootstrap.BootstrapConfiguration= org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration, org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration
所以ConfigServiceBootstrapConfiguration类会被Spring Boot默认进行自动加载装配的,在该类中我们看到了按条件加载装配的ConfigServicePropertySourceLocator,代码如下:
@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(name = ConfigClientProperties.PREFIX + ".enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
return new ConfigServicePropertySourceLocator(properties);
}
当配置文件中配置了spring.cloud.config.enabled为true,或缺失时都会自动装配ConfigServicePropertySourceLocator。从远程配置中心获取配置信息的功能就是在ConfigServicePropertySourceLocator类中实现的。
2. ConfigServicePropertySourceLocator类的功能
ConfigServicePropertySourceLocator类实现了PropertySourceLocator接口,
PropertySourceLocator接口的定义如下:
public interface PropertySourceLocator {
PropertySource> locate(Environment environment);
default Collection> locateCollection(Environment environment) {
return locateCollection(this, environment);
}
static Collection> locateCollection(PropertySourceLocator locator, Environment environment) {
PropertySource> propertySource = locator.locate(environment);
if (propertySource == null) {
return Collections.emptyList();
}
if (CompositePropertySource.class.isInstance(propertySource)) {
Collection> sources = ((CompositePropertySource) propertySource).getPropertySources();
List> filteredSources = new ArrayList<>();
for (PropertySource> p : sources) {
if (p != null) {
filteredSources.add(p);
}
}
return filteredSources;
}
else {
return Arrays.asList(propertySource);
}
}
}
locate抽象方法用于实现从远程配置中心获取指定的配置信息,Spring Cloud Config中把配置信息抽象为应用(application)、环境(profile)和版本(label)这三个维度进行管理,通过这三个维度,我们就可以确定唯一的一份配置数据。
ConfigServicePropertySourceLocator类中locate方法的实现
public org.springframework.core.env.PropertySource> locate(org.springframework.core.env.Environment environment) {
ConfigClientProperties properties = this.defaultProperties.override(environment);
CompositePropertySource composite = new OriginTrackedCompositePropertySource("configService");
ConfigClientRequestTemplateFactory requestTemplateFactory = new ConfigClientRequestTemplateFactory(logger,
properties);
Exception error = null;
String errorBody = null;
try {
String[] labels = new String[] { "" };
if (StringUtils.hasText(properties.getLabel())) {
labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel());
}
String state = ConfigClientStateHolder.getState();
// Try all the labels until one works
for (String label : labels) {
Environment result = getRemoteEnvironment(requestTemplateFactory, label.trim(), state);
if (result != null) {
log(result);
// result.getPropertySources() can be null if using xml
if (result.getPropertySources() != null) {
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map map = translateOrigins(source.getName(),
(Map) source.getSource());
composite.addPropertySource(new OriginTrackedMapPropertySource(source.getName(), map));
}
}
HashMap map = new HashMap<>();
if (StringUtils.hasText(result.getState())) {
putValue(map, "config.client.state", result.getState());
}
if (StringUtils.hasText(result.getVersion())) {
putValue(map, "config.client.version", result.getVersion());
}
// the existence of this property source confirms a successful
// response from config server
composite.addFirstPropertySource(new MapPropertySource("configClient", map));
return composite;
}
}
errorBody = String.format("None of labels %s found", Arrays.toString(labels));
}
catch (HttpServerErrorException e) {
error = e;
if (MediaType.APPLICATION_JSON.includes(e.getResponseHeaders().getContentType())) {
errorBody = e.getResponseBodyAsString();
}
}
catch (Exception e) {
error = e;
}
if (properties.isFailFast()) {
throw new IllegalStateException("Could not locate PropertySource and the fail fast property is set, failing"
+ (errorBody == null ? "" : ": " + errorBody), error);
}
logger.warn("Could not locate PropertySource: " + (error != null ? error.getMessage() : errorBody));
return null;
}
该方法的核心是从getRemoteEnvironment方法获取远程配置中心的指定配置内容。
private Environment getRemoteEnvironment(ConfigClientRequestTemplateFactory requestTemplateFactory, String label,
String state) {
//获取RestTemplate对象,该对象用于调用Restful接口
RestTemplate restTemplate = this.restTemplate == null ? requestTemplateFactory.create() : this.restTemplate;
//获取配置属性
ConfigClientProperties properties = requestTemplateFactory.getProperties();
String path = "/{name}/{profile}";
String name = properties.getName(); //配置文件中的spring.application.name
String profile = properties.getProfile(); //默认是default
String token = properties.getToken();
int noOfUrls = properties.getUri().length; //获取配置的uri的数量
if (noOfUrls > 1) {
logger.info("Multiple Config Server Urls found listed.");
}
//构建请求路径
Object[] args = new String[] { name, profile };
if (StringUtils.hasText(label)) {
// workaround for Spring MVC matching / in paths
label = Environment.denormalize(label);
args = new String[] { name, profile, label };
path = path + "/{label}";
}
ResponseEntity response = null;
List acceptHeader = Collections.singletonList(MediaType.parseMediaType(properties.getMediaType()));
for (int i = 0; i < noOfUrls; i++) {
Credentials credentials = properties.getCredentials(i);
String uri = credentials.getUri();
String username = credentials.getUsername();
String password = credentials.getPassword();
logger.info("Fetching config from server at : " + uri);
try {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptHeader);
requestTemplateFactory.addAuthorizationToken(headers, username, password);
if (StringUtils.hasText(token)) {
headers.add(TOKEN_HEADER, token);
}
if (StringUtils.hasText(state) && properties.isSendState()) {
headers.add(STATE_HEADER, state);
}
final HttpEntity entity = new HttpEntity<>((Void) null, headers);
//远程调用获取对应的配置信息
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args);
}
catch (HttpClientErrorException e) {
if (e.getStatusCode() != HttpStatus.NOT_FOUND) {
throw e;
}
}
catch (ResourceAccessException e) {
logger.info("Connect Timeout Exception on Url - " + uri + ". Will be trying the next url if available");
if (i == noOfUrls - 1) {
throw e;
}
else {
continue;
}
}
if (response == null || response.getStatusCode() != HttpStatus.OK) {
return null;
}
Environment result = response.getBody();
return result;
}
return null;
}
上述代码的主要流程就是获取访问配置服务器所需的 application、profile、label 等参数,然后利用 RestTemplate 工具类执行 HTTP 请求。客户端从这个请求所返回的 Environment 对象中获得所需要的各项配置信息。
3. PropertySourceBootstrapConfiguration类的自动装配
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration
implements ApplicationContextInitializer, Ordered
ropertySourceBootstrapProperties类的定义
@ConfigurationProperties("spring.cloud.config")
public class PropertySourceBootstrapProperties
我们从以上类的定义中我们看到当引入spring.cloud.config配置就会自动装配PropertySourceBootstrapConfiguration类,并且该类实现了ApplicationContextInitializer接口。
PropertySourceBootstrapConfiguration加载时会自动获取Spring 容器中的PropertySourceLocator Bean。
@Autowired(required = false) private ListpropertySourceLocators = new ArrayList<>();
PropertySourceBootstrapConfiguration 实现了 ApplicationContextInitializer 接口中的 initialize 方法,而所有的 ApplicationContextInitializer 都会在 Spring Boot 应用程序启动时进行加载。这样,当类路径中引入了 Spring Cloud Config 之后,一个 ConfigServicePropertySourceLocator 实例就会被构建并保存在 PropertySourceBootstrapConfiguration 的 propertySources中。然后,我们会遍历所有 propertySourceLocators 的 locate 方法,从而完成对远程服务配置信息的读取。
initialize方法定义如下:
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List> composite = new ArrayList<>();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
Collection> source = locator.locateCollection(environment);
if (source == null || source.size() == 0) {
continue;
}
List> sourceList = new ArrayList<>();
for (PropertySource> p : source) {
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource> enumerable = (EnumerablePropertySource>) p;
sourceList.add(new BootstrapPropertySource<>(enumerable));
}
else {
sourceList.add(new SimpleBootstrapPropertySource(p));
}
}
logger.info("Located property source: " + sourceList);
composite.addAll(sourceList);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
for (PropertySource> p : environment.getPropertySources()) {
if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(p.getName());
}
}
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
看到调用locateCollection方法获取远程配置中心数据,如果获取的数据不为空,则会调用insertPropertySources方法来注入获取到的配置信息。



