6. Config Center 源码
分布式配置中心服务端
org.springframework.cloud.config.server.config.ConfigServerAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class,
ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class,
ConfigServerMvcConfiguration.class, ResourceEncryptorConfiguration.class })
public class ConfigServerAutoConfiguration {
}
接收获取配置信息请求的 Controller
org.springframework.cloud.config.server.config.ConfigServerMvcConfiguration
@Bean
@RefreshScope // 属性可以刷新
public EnvironmentController environmentController(
EnvironmentRepository envRepository, ConfigServerProperties server) {
EnvironmentController controller = new EnvironmentController(
encrypted(envRepository, server), this.objectMapper);
controller.setStripDocumentFromYaml(server.isStripDocumentFromYaml());
controller.setAcceptEmpty(server.isAcceptEmpty());
return controller;
}
这是其中一个请求
org.springframework.cloud.config.server.environment.EnvironmentController#properties
@RequestMapping("/{name}-{profiles}.properties")
public ResponseEntity<String> properties(@PathVariable String name,
@PathVariable String profiles,
@RequestParam(defaultValue = "true") boolean resolvePlaceholders)
throws IOException {
return labelledProperties(name, profiles, null, resolvePlaceholders);
}
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
return getEnvironment(name, profiles, label, false);
}
// 获取环境数据
public Environment getEnvironment(String name, String profiles, String label,
boolean includeOrigin) {
if (name != null && name.contains("(_)")) {
// "(_)" is uncommon in a git repo name, but "/" cannot be matched
// by Spring MVC
name = name.replace("(_)", "/");
}
if (label != null && label.contains("(_)")) {
// "(_)" is uncommon in a git branch name, but "/" cannot be matched
// by Spring MVC
label = label.replace("(_)", "/");
}
// 去库中查询
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;
}
在这里就从 github(gitee也可以) 上 clone,和更新配置信息并且保存到了本地
其实可以读取的位置很多, 我们可以从实现类看出来(组合,本地,db,redis)
EnvironmentRepository#findOne(String,String, String, boolean)
当然我们要看的不是这个方法,是下面那个
AbstractScmEnvironmentRepository#findOne(String, String, String, boolean)
@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());
// 委托给NativeEnvironmentRepository执行
Environment result = delegate.findOne(application, profile, "", includeOrigin);
result.setVersion(locations.getVersion());
result.setLabel(label);
return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
getUri());
}
org.springframework.cloud.config.server.environment.JGitEnvironmentRepository#getLocations
@Override
public synchronized Locations getLocations(String application, String profile,
String label) {
if (label == null) {
label = this.defaultLabel;
}
String version = refresh(label); // 根据标签刷新
return new Locations(application, profile, label, version,
getSearchLocations(getWorkingDirectory(), application, profile, label));
}
org.springframework.cloud.config.server.environment.JGitEnvironmentRepository#refresh
public String refresh(String label) {
Git git = null;
try {
git = createGitClient();
if (shouldPull(git)) {
org.springframework.cloud.config.server.environment.JGitEnvironmentRepository#createGitClient
private Git createGitClient() throws IOException, GitAPIException {
File lock = new File(getWorkingDirectory(), ".git/index.lock");
if (lock.exists()) {
// The only way this can happen is if another JVM (e.g. one that
// crashed earlier) created the lock. We can attempt to recover by
// wiping the slate clean.
this.logger.info("Deleting stale JGit lock file at " + lock);
lock.delete();
}
if (new File(getWorkingDirectory(), ".git").exists()) {
return openGitRepository();
}
else {
return copyRepository(); // 复制
}
}
private synchronized Git copyRepository() throws IOException, GitAPIException {
deleteBaseDirIfExists();
getBasedir().mkdirs();
Assert.state(getBasedir().exists(), "Could not create basedir: " + getBasedir());
if (getUri().startsWith(FILE_URI_PREFIX)) {
return copyFromLocalRepository();
}
else {
return cloneToBasedir(); // 克隆基本目录
}
}
private Git cloneToBasedir() throws GitAPIException {
CloneCommand clone = this.gitFactory.getCloneCommandByCloneRepository()
.setURI(getUri()).setDirectory(getBasedir());
configureCommand(clone);
try {
return clone.call(); // 执行克隆命令
}
catch (GitAPIException e) {
this.logger.warn("Error occured cloning to base directory.", e);
deleteBaseDirIfExists();
throw e;
}
}
服务端的加密 controller
org.springframework.cloud.config.server.config.ConfigServerEncryptionConfiguration
@Configuration(proxyBeanMethods = false)
public class ConfigServerEncryptionConfiguration {
@Autowired(required = false)
private TextEncryptorLocator encryptor;
@Autowired
private ConfigServerProperties properties;
@Bean
public EncryptionController encryptionController() { // 装配controller
EncryptionController controller = new EncryptionController(this.encryptor);
controller.setDefaultApplicationName(this.properties.getDefaultApplicationName());
controller.setDefaultProfile(this.properties.getDefaultProfile());
return controller;
}
}
encrypt的mapping
EncryptionController#encrypt(java.lang.String, org.springframework.http.MediaType)
@RequestMapping(value = "encrypt", method = RequestMethod.POST)
public String encrypt(@RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
return encrypt(defaultApplicationName, defaultProfile, data, type);
}
@RequestMapping(value = "/encrypt/{name}/{profiles}", method = RequestMethod.POST)
public String encrypt(@PathVariable String name, @PathVariable String profiles,
@RequestBody String data, @RequestHeader("Content-Type") MediaType type) {
TextEncryptor encryptor = getEncryptor(name, profiles, "");
validateEncryptionWeakness(encryptor);
String input = stripFormData(data, type, false);
Map<String, String> keys = helper.getEncryptorKeys(name, profiles, input);
String textToEncrypt = helper.stripPrefix(input);
String encrypted = helper.addPrefix(keys,
encryptorLocator.locate(keys).encrypt(textToEncrypt));
logger.info("Encrypted data");
return encrypted;
}
Config客户端
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration
@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(
ConfigClientProperties properties) {
ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
properties);
return locator;
}
org.springframework.cloud.config.client.ConfigServicePropertySourceLocator#locate
这个方法就发起了对 config 服务端的调用获取服务列表
for (String label : labels) {
// 获取远程的环境信息
Environment result = getRemoteEnvironment(restTemplate, properties,
label.trim(), state);
if (result != null) {
log(result);
...
return composite;
}
}
org.springframework.cloud.config.client.ConfigServicePropertySourceLocator#getRemoteEnvironment
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(
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); // 封装成entity
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
Environment.class, args);
}
catch (HttpClientErrorException e) {
}
if (response == null || response.getStatusCode() != HttpStatus.OK) {
return null;
}
Environment result = response.getBody();
return result;
}
配置的动态刷新
当调用 http://localhost:8080/actuator/refresh
接口的时候就会调用到这个类
@Endpoint(id = "refresh")
public class RefreshEndpoint {
private ContextRefresher contextRefresher;
public RefreshEndpoint(ContextRefresher contextRefresher) {
this.contextRefresher = contextRefresher;
}
@WriteOperation
public Collection<String> refresh() { // 刷新
Set<String> keys = this.contextRefresher.refresh();
return keys;
}
}
org.springframework.cloud.context.refresh.ContextRefresher#refresh
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
先更新 spring 容器的属性,然后再更新 @RefreshScope 注解的类刷新,其实就是自定义 scope,由自己维护实例