spring cloud config源碼 client(一)
整體架構模塊
client整體包大體類結構介紹
client入口我們先看spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.config.client.ConfigClientAutoConfiguration
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration
這個文件是自動配置和啓動相關的配置文件,spring.factories 仿造的是java的spi機制(這個不是重點)。我們來關注下這兩個入口類。先看ConfigClientAutoConfiguration.
ConfigClientAutoConfiguration
整體代碼如下:
/**
* 這個類的主要作用就是配置好自身要拉去遠程resource的相關信息,是否開啓心跳,及是否開啓刷新監控
**/
/**
* Expose a ConfigClientProperties just so that there is a way to inspect the properties
* bound to it. It won't be available in time for autowiring into the bootstrap context,
* but the values in this properties object will be the same as the ones used to bind to
* the config server, if there is one.
*
* @author Dave Syer
* @author Marcos Barbero
*
*/
@Configuration
public class ConfigClientAutoConfiguration {
/**
* 第一個是獲取連接到Configserver的相關配置 比如label profile applicaitonname這些屬性都在ConfigClientProperties
* 如果bootstrap上下文有的話從 bootstrap上下文中獲取到這個配置(涉及到bootstrap 和applicaiton區別 ps:bootstrap.yaml是applcation的父上下文)
* @param environment
* @param context
* @return
*/
@Bean
public ConfigClientProperties configClientProperties(Environment environment,
ApplicationContext context) {
if (context.getParent() != null
&& BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
context.getParent(), ConfigClientProperties.class).length > 0) {
return BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(),
ConfigClientProperties.class);
}
ConfigClientProperties client = new ConfigClientProperties(environment);
return client;
}
/**
* 心跳檢測相關的配置
* @return
*/
@Bean
public ConfigClientHealthProperties configClientHealthProperties() {
return new ConfigClientHealthProperties();
}
/**
* 如果引入了actuator的話這裏會成立 心跳指示器
*/
@Configuration
@ConditionalOnClass(HealthIndicator.class)
@ConditionalOnBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "health.config.enabled", matchIfMissing = true)
protected static class ConfigServerHealthIndicatorConfiguration {
@Bean
public ConfigServerHealthIndicator configServerHealthIndicator(
ConfigServicePropertySourceLocator locator,
ConfigClientHealthProperties properties, Environment environment) {
return new ConfigServerHealthIndicator(locator, environment, properties);
}
}
@Configuration
@ConditionalOnClass(ContextRefresher.class)
@ConditionalOnBean(ContextRefresher.class)
@ConditionalOnProperty(value = "spring.cloud.config.watch.enabled")
protected static class ConfigClientWatchConfiguration {
/**
* 起定時任務觀察是否發生狀態配置變化,如果發現了會調用RefreshScope.refreshAll 完成全量刷新 (帶@RefreshScope註解的bean)
* config.client.state
* @param contextRefresher
* @return
*/
@Bean
public ConfigClientWatch configClientWatch(ContextRefresher contextRefresher) {
return new ConfigClientWatch(contextRefresher);
}
}
}
ConfigServiceBootstrapConfiguration
/**
* 引導啓動類
* @author Dave Syer
* @author Tristan Hanson
*
*/
@Configuration
@EnableConfigurationProperties
public class ConfigServiceBootstrapConfiguration {
@Autowired
private ConfigurableEnvironment environment;
/**
* 配置要獲取到config的相關配置類 bootstrap對應的上下文配置愛configclientprop
* @return
*/
@Bean
public ConfigClientProperties configClientProperties() {
ConfigClientProperties client = new ConfigClientProperties(this.environment);
return client;
}
/**
* configService ps定位類 重點拉取遠程配置的類 我們在下方貼出重點代碼
* @param properties
* @return
*/
@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
//改類內部封裝了重試獲取遠程配置的方法
ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
properties);
return locator;
}
@ConditionalOnProperty(value = "spring.cloud.config.fail-fast")
@ConditionalOnClass({ Retryable.class, Aspect.class, AopAutoConfiguration.class })
@Configuration
@EnableRetry(proxyTargetClass = true)
@Import(AopAutoConfiguration.class)
@EnableConfigurationProperties(RetryProperties.class)
protected static class RetryConfiguration {
/**
* 重試攔截器 這裏暫時不是重點指導是什麼就好
* @param properties
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "configServerRetryInterceptor")
public RetryOperationsInterceptor configServerRetryInterceptor(
RetryProperties properties) {
return RetryInterceptorBuilder
.stateless()
.backOffOptions(properties.getInitialInterval(),
properties.getMultiplier(), properties.getMaxInterval())
.maxAttempts(properties.getMaxAttempts()).build();
}
}
}
ConfigServicePropertySourceLocator相關的重點代碼及註釋 這個方法locate獲取到配置數據:
大體邏輯是通過configclientprop組裝 /{applicaiton}/{profile}/{label} +上configclientprop對應的configserver的uri,然後請求獲取配置,拉取配置後存入CompositePropertySource名字是"configService")。(重點這裏通過ConfgiClientProperties裏面的uir作爲homepage,在啓用discoveryclient的時候會被替換);
@Override
@Retryable(interceptor = "configServerRetryInterceptor")
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
//從環境中重新獲取覆蓋
ConfigClientProperties properties = this.defaultProperties.override(environment);
//創建composite 這個類持有一個鏈表的PropertySource 本身也是PropertySource的一個實現
CompositePropertySource composite = new CompositePropertySource("configService");
//請求模板 用於請求Remote
RestTemplate restTemplate = this.restTemplate == null
? getSecureRestTemplate(properties)
: this.restTemplate;
Exception error = null;
String errorBody = null;
try {
String[] labels = new String[] { "" };
//把lables 拆出來成數組
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(springcloud封裝的) 從configserver獲取到
Environment result = getRemoteEnvironment(restTemplate, properties,
label.trim(), state);
if (result != null) {
log(result);
if (result.getPropertySources() != null) { // result.getPropertySources()
// can be null if using
// xml
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) source
.getSource();
composite.addPropertySource(
new MapPropertySource(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;
}
}
}
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: " + (errorBody == null
? error == null ? "label not found" : error.getMessage()
: errorBody));
return null;
}
uml圖如下:
ConfigServerLocator uml:
接下來第三個引導類
DiscoveryClientConfigServiceBootstrapConfiguration
我們來看下面的註釋及對應的代碼
**
* 配置客戶端引導配置通過discovery來查找configserver,
* Bootstrap configuration for a config client that wants to lookup the config server via
* discovery.
*
* @author Dave Syer
*/
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@Import({ UtilAutoConfiguration.class })//主要引入兩個類本地網絡配置的兩個類
@EnableDiscoveryClient
public class DiscoveryClientConfigServiceBootstrapConfiguration {
private static Log logger = LogFactory
.getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class);
@Autowired
private ConfigClientProperties config;
@Autowired
private ConfigServerInstanceProvider instanceProvider;
private HeartbeatMonitor monitor = new HeartbeatMonitor();
//通過註冊中心 獲取到configserver列表
@Bean
public ConfigServerInstanceProvider configServerInstanceProvider( <1>
DiscoveryClient discoveryClient) {
return new ConfigServerInstanceProvider(discoveryClient);
}
/**
* 監聽上下文刷新事件,遠程通知的時候會被調用這個事件
* @param event
*/
@EventListener(ContextRefreshedEvent.class)
public void startup(ContextRefreshedEvent event) {
refresh();
}
/**
* 心跳檢測事件
* @param event
*/
@EventListener(HeartbeatEvent.class)
public void heartbeat(HeartbeatEvent event) {
if (monitor.update(event.getValue())) {
refresh();
}
}
/**
* 刷新ConfigClientProperties對應的uri(configserver) <2>
*/
private void refresh() {
try {
String serviceId = this.config.getDiscovery().getServiceId();
List<String> listOfUrls = new ArrayList<>();
List<ServiceInstance> serviceInstances = this.instanceProvider
.getConfigServerInstances(serviceId);
for (int i = 0; i < serviceInstances.size(); i++) {
ServiceInstance server = serviceInstances.get(i);
String url = getHomePage(server);
if (server.getMetadata().containsKey("password")) {
String user = server.getMetadata().get("user");
user = user == null ? "user" : user;
this.config.setUsername(user);
String password = server.getMetadata().get("password");
this.config.setPassword(password);
}
if (server.getMetadata().containsKey("configPath")) {
String path = server.getMetadata().get("configPath");
if (url.endsWith("/") && path.startsWith("/")) {
url = url.substring(0, url.length() - 1);
}
url = url + path;
}
listOfUrls.add(url);
}
String[] uri = new String[listOfUrls.size()];
uri = listOfUrls.toArray(uri);
this.config.setUri(uri);
}
catch (Exception ex) {
if (config.isFailFast()) {
throw ex;
}
else {
logger.warn("Could not locate configserver via discovery", ex);
}
}
}
private String getHomePage(ServiceInstance server) {
return server.getUri().toString() + "/";
}
}
主要配置說明:1.ConfigserverProvider 這裏通過DiscoveryClient封裝獲取到ConfigServer對應的實例列表。
2.refresh在啓動時被調用:調用的時候會將ConfigclientProperty這個對象裏面的uri轉換成現有的configserver實例對應的homepage “," 隔開的uri。這樣通過
configclient的代碼就上方這麼點,如果要獲取配置就可以通過ConfigServicePropertySourceLocator完成配置,本身這塊代碼也很簡單,後續我們補充下cloud context是怎麼通過這個類完成遠程配置拉取的。