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,由自己维护实例










