Spring Cloud Config 的交互流程
2020-07-24 本文已影响0人
蓝笔头
1. demo 搭建
-
configserver
项目搭建参考: Spring Cloud Config 的 refresh 机制。 -
config-client-demo
项目参考: Spring Cloud Config 的 refresh 机制。
2. config-client 源码解析
直接看 ConfigServicePropertySourceLocator
中从 configserver
获取配置的相关代码。
package org.springframework.cloud.config.client;
@Order(0)
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
@Override
@Retryable(interceptor = "configServerRetryInterceptor")
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
ConfigClientProperties properties = this.defaultProperties.override(environment);
CompositePropertySource composite = new OriginTrackedCompositePropertySource(
"configService");
RestTemplate restTemplate = this.restTemplate == null
? getSecureRestTemplate(properties) : this.restTemplate;
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) {
// 1. 从 configserver 获取配置
Environment result = getRemoteEnvironment(restTemplate, properties,
label.trim(), state);
if (result != null) {
log(result);
// result.getPropertySources() can be null if using xml
if (result.getPropertySources() != null) {
// 配置通过 Environment 中的 PropertySource 字段返回
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = translateOrigins(source.getName(),
(Map<String, Object>) source.getSource());
composite.addPropertySource(
new OriginTrackedMapPropertySource(source.getName(),
map));
}
}
if (StringUtils.hasText(result.getState())
|| StringUtils.hasText(result.getVersion())) {
HashMap<String, Object> map = new HashMap<>();
putValue(map, "config.client.state", result.getState());
putValue(map, "config.client.version", result.getVersion());
composite.addFirstPropertySource(
new MapPropertySource("configClient", map));
}
return composite;
}
}
}
}
private Environment getRemoteEnvironment(RestTemplate restTemplate,
ConfigClientProperties properties, String label, String state) {
String path = "/{name}/{profile}";
String name = properties.getName();
String profile = properties.getProfile();
String token = properties.getToken();
int noOfUrls = properties.getUri().length;
if (noOfUrls > 1) {
logger.info("Multiple Config Server Urls found listed.");
}
Object[] args = new String[] { name, profile };
if (StringUtils.hasText(label)) {
if (label.contains("/")) {
label = label.replace("/", "(_)");
}
args = new String[] { name, profile, label };
path = path + "/{label}";
}
ResponseEntity<Environment> response = null;
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();
// 设置 Accept 为 "application/vnd.spring-cloud.config-server.v2+json",用来匹配 configserver 中的接口的 @RequestMapping 注解的 produces 属性
headers.setAccept(
Collections.singletonList(MediaType.parseMediaType(V2_JSON)));
addAuthorizationToken(properties, 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<Void> entity = new HttpEntity<>((Void) null, headers);
// 2. 没有 label 的情况下,调用 configserver 的 /{name}/{profile} 接口户获取配置信息,配置信息通过 Environment 类型返回
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
Environment.class, args);
}
Environment result = response.getBody();
return result;
}
return null;
}
}
从上述代码中,我们可以得知,config-client
是通过调用 configserver
的 /{name}/{profile}
接口户获取配置信息。
3. configserver 源码分析
3.1 EnvironmentController
类
package org.springframework.cloud.config.server.environment;
@RestController
@RequestMapping(method = RequestMethod.GET,
path = "${spring.cloud.config.server.prefix:}")
public class EnvironmentController {
private EnvironmentRepository repository;
@RequestMapping(path = "/{name}/{profiles:.*[^-].*}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Environment defaultLabel(@PathVariable String name,
@PathVariable String profiles) {
return getEnvironment(name, profiles, null, false);
}
// config-client 调用此接口
// Accept 为 "application/vnd.spring-cloud.config-server.v2+json" 匹配 produces = EnvironmentMediaType.V2_JSON
@RequestMapping(path = "/{name}/{profiles:.*[^-].*}",
produces = EnvironmentMediaType.V2_JSON)
public Environment defaultLabelIncludeOrigin(@PathVariable String name,
@PathVariable String profiles) {
return getEnvironment(name, profiles, null, true);
}
public Environment getEnvironment(String name, String profiles, String label,
boolean includeOrigin) {
name = normalize(name);
label = normalize(label);
Environment environment = this.repository.findOne(name, profiles, label,
includeOrigin);
if (!this.acceptEmpty
&& (environment == null || environment.getPropertySources().isEmpty())) {
throw new EnvironmentNotFoundException("Profile Not found");
}
return environment;
}
}
后续测试可以通过手动调用 http://localhost:8888/application/dev 接口,来触发 configserver 的
getEnvironment
逻辑。
3.2 NativeEnvironmentRepository
类
getEnvironment debug 截图.png
根据 debug 查看上下文,发现最终是通过 NativeEnvironmentRepository
类来生成 Environment
的。
package org.springframework.cloud.config.server.environment;
public class NativeEnvironmentRepository
implements EnvironmentRepository, SearchPathLocator, Ordered {
private static final String[] DEFAULT_LOCATIONS = new String[] { "classpath:/",
"classpath:/config/", "file:./", "file:./config/" };
/**
* Locations to search for configuration files. Defaults to the same as a Spring Boot
* app so [classpath:/,classpath:/config/,file:./,file:./config/].
*/
private String[] searchLocations;
private ConfigurableEnvironment environment;
@Override
public Environment findOne(String config, String profile, String label,
boolean includeOrigin) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(
PropertyPlaceholderAutoConfiguration.class);
ConfigurableEnvironment environment = getEnvironment(profile);
builder.environment(environment);
builder.web(WebApplicationType.NONE).bannerMode(Mode.OFF);
if (!logger.isDebugEnabled()) {
// Make the mini-application startup less verbose
builder.logStartupInfo(false);
}
String[] args = getArgs(config, profile, label);
// Explicitly set the listeners (to exclude logging listener which would change
// log levels in the caller)
builder.application()
.setListeners(Arrays.asList(new ConfigFileApplicationListener()));
// ConfigFileApplicationListener 中加载 spring.config.location 和 spring.config.name 参数的匹配的配置到 environment 中
try (ConfigurableApplicationContext context = builder.run(args)) {
environment.getPropertySources().remove("profiles");
// PassthruEnvironmentRepository.findOne 过滤一些系统相关的配置
// clean 中过滤不匹配的配置
// 最后返回符号条件的配置
return clean(new PassthruEnvironmentRepository(environment).findOne(config,
profile, label, includeOrigin));
}
}
private String[] getArgs(String application, String profile, String label) {
List<String> list = new ArrayList<String>();
String config = application;
if (!config.startsWith("application")) {
config = "application," + config;
}
list.add("--spring.config.name=" + config);
list.add("--spring.cloud.bootstrap.enabled=false");
list.add("--encrypt.failOnError=" + this.failOnError);
list.add("--spring.config.location=" + StringUtils.arrayToCommaDelimitedString(
getLocations(application, profile, label).getLocations()));
return list.toArray(new String[0]);
}
}
(完)