Spring Cloud Config
目标
- 在分布式系统中,提供一个机制,去管理外置配置,配置变更之后,进行生效,刷新配置
实现
-
类似于 Spring 应用中的 Environment 与 PropertySource
-
在上下文中增加 Spring Cloud Config 的 PropertySource
Spring Cloud Config 的 PropertySource
PropertySource
- Spring Cloud Config Client - 使用 Spring Cloud 自己的 CompositePropertySource
- Zookeeper - 有对应的 ZookeeperPropertySource
- Consul - ConsulPropertySource / ConsulFilesPropertySource
这些 不同的 PropertySource 的优先级会高于本身的配置文件对应的PropertySource
PropertySourceLocator
- 通过 PropertySourceLocator 找到提供 PropertySource,不同的配置中心有不同的 PropertySourceLocator
源码分析
ConfigServicePropertySourceLocator
@Order(0)
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
private static Log logger = LogFactory.getLog(ConfigServicePropertySourceLocator.class);
private RestTemplate restTemplate;
private ConfigClientProperties defaultProperties;
public ConfigServicePropertySourceLocator(ConfigClientProperties defaultProperties) {
this.defaultProperties = defaultProperties;
}
@Retryable(
interceptor = "configServerRetryInterceptor"
)
public PropertySource<?> locate(Environment environment) {
ConfigClientProperties properties = this.defaultProperties.override(environment);
CompositePropertySource composite = new CompositePropertySource("configService");
RestTemplate restTemplate = this.restTemplate == null ? this.getSecureRestTemplate(properties) : this.restTemplate;
Exception error = null;
String errorBody = null;
// 设置默认的属性 创建了一个 CompositePropertySource 检测是否存在 restTemplate 没有的话 自己创建一个
try {
String[] labels = new String[]{""};
if (StringUtils.hasText(properties.getLabel())) {
labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel());
}
// 做一个 label 的设置 如果有就设置进去 如果 没有 设置为空
String state = ConfigClientStateHolder.getState();
String[] var9 = labels;
int var10 = labels.length;
for(int var11 = 0; var11 < var10; ++var11) { //对不同的 label 做一个遍历 通过 getRemoteEnvironment 用 restTemplate 对应的 properties 去找一个 Environment 如果 找出来 有东西 就从中找出 对应的 PropertySources 这些 PropertySources 每个都取出来之后 加到composite 中
String label = var9[var11];
org.springframework.cloud.config.environment.Environment result = this.getRemoteEnvironment(restTemplate, properties, label.trim(), state);
if (result != null) {
this.log(result);
if (result.getPropertySources() != null) {
Iterator var14 = result.getPropertySources().iterator();
while(var14.hasNext()) {
org.springframework.cloud.config.environment.PropertySource source = (org.springframework.cloud.config.environment.PropertySource)var14.next();
Map<String, Object> map = source.getSource();
composite.addPropertySource(new MapPropertySource(source.getName(), map));
}
}
if (StringUtils.hasText(result.getState()) || StringUtils.hasText(result.getVersion())) {
HashMap<String, Object> map = new HashMap();
this.putValue(map, "config.client.state", result.getState());
this.putValue(map, "config.client.version", result.getVersion());
composite.addFirstPropertySource(new MapPropertySource("configClient", map));
}
return composite;
}
}
} catch (HttpServerErrorException var17) {
error = var17;
if (MediaType.APPLICATION_JSON.includes(var17.getResponseHeaders().getContentType())) {
errorBody = var17.getResponseBodyAsString();
}
} catch (Exception var18) {
error = var18;
}
if (properties.isFailFast()) {
throw new IllegalStateException("Could not locate PropertySource and the fail fast property is set, failing" + (errorBody == null ? "" : ": " + errorBody), (Throwable)error);
} else {
logger.warn("Could not locate PropertySource: " + (errorBody == null ? (error == null ? "label not found" : ((Exception)error).getMessage()) : errorBody));
return null;
}
}
private void log(org.springframework.cloud.config.environment.Environment result) {
if (logger.isInfoEnabled()) {
logger.info(String.format("Located environment: name=%s, profiles=%s, label=%s, version=%s, state=%s", result.getName(), result.getProfiles() == null ? "" : Arrays.asList(result.getProfiles()), result.getLabel(), result.getVersion(), result.getState()));
}
if (logger.isDebugEnabled()) {
List<org.springframework.cloud.config.environment.PropertySource> propertySourceList = result.getPropertySources();
if (propertySourceList != null) {
int propertyCount = 0;
org.springframework.cloud.config.environment.PropertySource propertySource;
for(Iterator var4 = propertySourceList.iterator(); var4.hasNext(); propertyCount += propertySource.getSource().size()) {
propertySource = (org.springframework.cloud.config.environment.PropertySource)var4.next();
}
logger.debug(String.format("Environment %s has %d property sources with %d properties.", result.getName(), result.getPropertySources().size(), propertyCount));
}
}
}
private void putValue(HashMap<String, Object> map, String key, String value) {
if (StringUtils.hasText(value)) {
map.put(key, value);
}
}
private org.springframework.cloud.config.environment.Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, String label, String state) {//去拼了一个uri 做了一个调用
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)) { //如果有 label 将 label 带上去
if (label.contains("/")) {
label = label.replace("/", "(_)");
}
args = new String[]{name, profile, label};
path = path + "/{label}";
}
ResponseEntity<org.springframework.cloud.config.environment.Environment> response = null;
int i = 0;
while(true) {
if (i < noOfUrls) {//用户名密码处理
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();
this.addAuthorizationToken(properties, headers, username, password);
if (StringUtils.hasText(token)) {
headers.add("X-Config-Token", token);
}
if (StringUtils.hasText(state) && properties.isSendState()) {
headers.add("X-Config-State", state);
}
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<Void> entity = new HttpEntity((Void)null, headers);
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, org.springframework.cloud.config.environment.Environment.class, args);//做一个http的get请求 取回来的是一个environment对象
} catch (HttpClientErrorException var19) {
if (var19.getStatusCode() != HttpStatus.NOT_FOUND) {
throw var19;
}
} catch (ResourceAccessException var20) {
logger.info("Connect Timeout Exception on Url - " + uri + ". Will be trying the next url if available");
if (i == noOfUrls - 1) {
throw var20;
}
++i;
continue;
}
if (response != null && response.getStatusCode() == HttpStatus.OK) {
org.springframework.cloud.config.environment.Environment result = (org.springframework.cloud.config.environment.Environment)response.getBody();
return result;
}
return null;
}
return null;
}
}
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private RestTemplate getSecureRestTemplate(ConfigClientProperties client) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
if (client.getRequestReadTimeout() < 0) {
throw new IllegalStateException("Invalid Value for Read Timeout set.");
} else {
requestFactory.setReadTimeout(client.getRequestReadTimeout());
RestTemplate template = new RestTemplate(requestFactory);
Map<String, String> headers = new HashMap(client.getHeaders());
if (headers.containsKey("authorization")) {
headers.remove("authorization");
}
if (!headers.isEmpty()) {
template.setInterceptors(Arrays.asList(new ConfigServicePropertySourceLocator.GenericRequestHeaderInterceptor(headers)));
}
return template;
}
}
private void addAuthorizationToken(ConfigClientProperties configClientProperties, HttpHeaders httpHeaders, String username, String password) {
String authorization = (String)configClientProperties.getHeaders().get("authorization");
if (password != null && authorization != null) {
throw new IllegalStateException("You must set either 'password' or 'authorization'");
} else {
if (password != null) {
byte[] token = Base64Utils.encode((username + ":" + password).getBytes());
httpHeaders.add("Authorization", "Basic " + new String(token));
} else if (authorization != null) {
httpHeaders.add("Authorization", authorization);
}
}
}
public static class GenericRequestHeaderInterceptor implements ClientHttpRequestInterceptor {
private final Map<String, String> headers;
public GenericRequestHeaderInterceptor(Map<String, String> headers) {
this.headers = headers;
}
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
Iterator var4 = this.headers.entrySet().iterator();
while(var4.hasNext()) {
Entry<String, String> header = (Entry)var4.next();
request.getHeaders().add((String)header.getKey(), (String)header.getValue());
}
return execution.execute(request, body);
}
protected Map<String, String> getHeaders() {
return this.headers;
}
}
}
PropertySourceLocator
public interface PropertySourceLocator {//实现了 这一个方法 从 Environment 中 找出 所有的 PropertySource
PropertySource<?> locate(Environment environment);
}
Spring Cloud Config Server
EnvironmentRepository
- Git / SVN / Vault (加密)/ JDBC …
功能特性
- SSH、代理访问、配置加密 …
配置刷新
- 访问 /actuator/refresh 这个 endpoint 进行 刷新
- 通过了Spring Cloud Bus这个机制 - 使用RefreshRemoteApplicationEvent这个事件 对集群中的所有成员做刷新
Spring Cloud Config Zookeeper
ZookeeperConfigBootstrapConfiguration
-
注册 ZookeeperPropertySourceLocator
• 提供 ZookeeperPropertySource -
ZookeeperConfigAutoConfiguration
• 注册 ConfigWatcher 去监听配置的节点 如果 配置项发生变化 去刷新内存中的值
源码分析
ZookeeperConfigBootstrapConfiguration
@Configuration
@ConditionalOnZookeeperEnabled
@Import({ZookeeperAutoConfiguration.class})
public class ZookeeperConfigBootstrapConfiguration {
public ZookeeperConfigBootstrapConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public ZookeeperPropertySourceLocator zookeeperPropertySourceLocator(CuratorFramework curator, ZookeeperConfigProperties properties) {//注册了一个 zookeeperPropertySourceLocator
return new ZookeeperPropertySourceLocator(curator, properties);
}
@Bean
@ConditionalOnMissingBean
public ZookeeperConfigProperties zookeeperConfigProperties() {
return new ZookeeperConfigProperties();
}
}
ZookeeperPropertySourceLocator
public class ZookeeperPropertySourceLocator implements PropertySourceLocator {
private ZookeeperConfigProperties properties;
private CuratorFramework curator;
private List<String> contexts;
private static final Log log = LogFactory.getLog(ZookeeperPropertySourceLocator.class);
public ZookeeperPropertySourceLocator(CuratorFramework curator, ZookeeperConfigProperties properties) {
this.curator = curator;
this.properties = properties;
}
public List<String> getContexts() {
return this.contexts;
}
public PropertySource<?> locate(Environment environment) {
if (!(environment instanceof ConfigurableEnvironment)) {
return null;
} else {
ConfigurableEnvironment env = (ConfigurableEnvironment)environment;
String appName = env.getProperty("spring.application.name"); //从Zookeeper中 取出应用名 profiles
if (appName == null) {
appName = "application";
log.warn("spring.application.name is not set. Using default of 'application'");
}
List<String> profiles = Arrays.asList(env.getActiveProfiles());
String root = this.properties.getRoot();
this.contexts = new ArrayList();
String defaultContext = root + "/" + this.properties.getDefaultContext();//取出相关信息
this.contexts.add(defaultContext);
this.addProfiles(this.contexts, defaultContext, profiles);
StringBuilder baseContext = new StringBuilder(root);
if (!appName.startsWith("/")) {
baseContext.append("/");
}
baseContext.append(appName);
this.contexts.add(baseContext.toString());
this.addProfiles(this.contexts, baseContext.toString(), profiles);
CompositePropertySource composite = new CompositePropertySource("zookeeper");
Collections.reverse(this.contexts);
Iterator var9 = this.contexts.iterator();
while(var9.hasNext()) {
String propertySourceContext = (String)var9.next();
try { //遍历 取出之后 创建PropertySource
PropertySource propertySource = this.create(propertySourceContext);
composite.addPropertySource(propertySource);//创建完了之后 加到composite中
} catch (Exception var12) {
if (this.properties.isFailFast()) {
ReflectionUtils.rethrowRuntimeException(var12);
} else {
log.warn("Unable to load zookeeper config from " + propertySourceContext, var12);
}
}
}
return composite;
}
}
@PreDestroy
public void destroy() {
}
private PropertySource<CuratorFramework> create(String context) {//使用传进来的context创建
return new ZookeeperPropertySource(context, this.curator);
}
private void addProfiles(List<String> contexts, String baseContext, List<String> profiles) {
Iterator var4 = profiles.iterator();
while(var4.hasNext()) {
String profile = (String)var4.next();
contexts.add(baseContext + this.properties.getProfileSeparator() + profile);
}
}
}
ZookeeperConfigAutoConfiguration
@Configuration
@ConditionalOnZookeeperEnabled
@ConditionalOnProperty(
value = {"spring.cloud.zookeeper.config.enabled"},
matchIfMissing = true
)
public class ZookeeperConfigAutoConfiguration {
public ZookeeperConfigAutoConfiguration() {
}
@Configuration
@ConditionalOnClass({RefreshEndpoint.class})
protected static class ZkRefreshConfiguration {
protected ZkRefreshConfiguration() {
}
@Bean
@ConditionalOnProperty(
name = {"spring.cloud.zookeeper.config.watcher.enabled"},
matchIfMissing = true
)//默认是开启的
public ConfigWatcher configWatcher(ZookeeperPropertySourceLocator locator, CuratorFramework curator) {
return new ConfigWatcher(locator.getContexts(), curator);
}
}
}
ConfigWatcher
public class ConfigWatcher implements Closeable, TreeCacheListener, ApplicationEventPublisherAware {
private static final Log log = LogFactory.getLog(ConfigWatcher.class);
private AtomicBoolean running = new AtomicBoolean(false);
private List<String> contexts;
private CuratorFramework source;
private ApplicationEventPublisher publisher;
private HashMap<String, TreeCache> caches;
public ConfigWatcher(List<String> contexts, CuratorFramework source) {
this.contexts = contexts;
this.source = source;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@PostConstruct
public void start() { //在ConfigWatcher创建完之后 start做了个监听
if (this.running.compareAndSet(false, true)) {
this.caches = new HashMap();
Iterator var1 = this.contexts.iterator();
while(var1.hasNext()) {
String context = (String)var1.next();
if (!context.startsWith("/")) {
context = "/" + context;
}
try {//创建一个 TreeCache 把自己作为一个监听器加入进去
TreeCache cache = TreeCache.newBuilder(this.source, context).build();
cache.getListenable().addListener(this);
cache.start();
this.caches.put(context, cache);
} catch (NoNodeException var4) {
;
} catch (Exception var5) {
log.error("Error initializing listener for context " + context, var5);
}
}
}
}
public void close() {
if (this.running.compareAndSet(true, false)) {
Iterator var1 = this.caches.values().iterator();
while(var1.hasNext()) {
TreeCache cache = (TreeCache)var1.next();
cache.close();
}
this.caches = null;
}
}
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {//收到 TreeCacheEvent 时 如果有增加删除更新节点的操作 它就会 使用publishEvent 创建一个RefreshEvent
Type eventType = event.getType();
if (eventType == Type.NODE_ADDED || eventType == Type.NODE_REMOVED || eventType == Type.NODE_UPDATED) {
this.publisher.publishEvent(new RefreshEvent(this, event, this.getEventDesc(event)));
}
}
public String getEventDesc(TreeCacheEvent event) {
StringBuilder out = new StringBuilder();
out.append("type=").append(event.getType());
out.append(", path=").append(event.getData().getPath());
byte[] data = event.getData().getData();
if (data != null && data.length > 0) {
out.append(", data=").append(new String(data, Charset.forName("UTF-8")));
}
return out.toString();
}
}
RefreshEvent
public class RefreshEvent extends ApplicationEvent {
private Object event;
private String eventDesc;
public RefreshEvent(Object source, Object event, String eventDesc) {
super(source);
this.event = event;
this.eventDesc = eventDesc;
}
public Object getEvent() {
return this.event;
}
public String getEventDesc() {
return this.eventDesc;
}
}
RefreshEventListener
public class RefreshEventListener implements SmartApplicationListener {
private static Log log = LogFactory.getLog(RefreshEventListener.class);
private ContextRefresher refresh;
private AtomicBoolean ready = new AtomicBoolean(false);
public RefreshEventListener(ContextRefresher refresh) {
this.refresh = refresh;
}
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationReadyEvent.class.isAssignableFrom(eventType) || RefreshEvent.class.isAssignableFrom(eventType);
}
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
this.handle((ApplicationReadyEvent)event);
} else if (event instanceof RefreshEvent) {
this.handle((RefreshEvent)event);
}
}
public void handle(ApplicationReadyEvent event) {
this.ready.compareAndSet(false, true);
}
public void handle(RefreshEvent event) {//在这里 就会去刷新RefreshEvent 和endpoint是一样的
if (this.ready.get()) {
log.debug("Event received " + event.getEventDesc());
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
}
配置的组合顺序
- 应用名-profile.yml
- 应用名.yml
- application-profile.yml
- application.yml
优先级 从上到下 依次递减