spring cloud config源码 client(一)
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是怎么通过这个类完成远程配置拉取的。
上一篇: 正则表达式查找相似单词的方法
推荐阅读
-
基于Spring注解的上下文初始化过程源码解析(一)
-
SpringCloud之分布式配置中心Spring Cloud Config高可用配置实例代码
-
spring cloud配置高可用eureka时遇到的一些坑
-
Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
-
spring-boot-2.0.3不一样系列之源码篇 - run方法(三)之createApplicationContext,绝对有值得你看的地方
-
Spring MVC源码(一) ----- 启动过程与组件初始化
-
Mybaits 源码解析 (十)----- 全网最详细,没有之一:Spring-Mybatis框架使用与源码解析
-
九、Spring之BeanFactory源码分析(一)
-
Spring Cloud Config 配置中心实践过程中,你需要了解这些细节!
-
[Spring cloud 一步步实现广告系统] 11. 使用Feign实现微服务调用