1. 基本使用
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
定义自己的配置类
@ConfigurationProperties(prefix = "myconfig")
public class MyConfig {
private String name;
private Boolean enabled;
}
Application注册
@EnableDiscoveryClient
@EnableConfigurationProperties({MyConfig.class})
@SpringBootApplication
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
使用
@RefreshScope //动态刷新
@RestController
public class MyController {
@Autowired
private MyConfig myConfig;
@ResponseBody
@RequestMapping("/config")
public Object testConfig() {
return myConfig;
}
}
2. 工作原理
2.1 启动
先看配置类先看配置类ConsulConfigProperties,如下是可以看到引入了spring-cloud-starter-consul-config以后就会自动启用了consul配置中心,并初始化了一个定时任务ConfigWatch,用于做配置的更新。
@Configuration(proxyBeanMethods = false)
@ConditionalOnConsulEnabled
@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled", matchIfMissing = true)
public class ConsulConfigAutoConfiguration {
/**
* Name of the config watch task scheduler bean.
*/
public static final String CONFIG_WATCH_TASK_SCHEDULER_NAME = "configWatchTaskScheduler";
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshEndpoint.class)
protected static class ConsulRefreshConfiguration {
@Bean
@ConditionalOnProperty(name = "spring.cloud.consul.config.watch.enabled",
matchIfMissing = true)
public ConfigWatch configWatch(ConsulConfigProperties properties,
ConsulPropertySourceLocator locator, ConsulClient consul,
@Qualifier(CONFIG_WATCH_TASK_SCHEDULER_NAME) TaskScheduler taskScheduler) {
return new ConfigWatch(properties, consul, locator.getContextIndexes(),
taskScheduler);
}
@Bean(name = CONFIG_WATCH_TASK_SCHEDULER_NAME)
@ConditionalOnProperty(name = "spring.cloud.consul.config.watch.enabled",
matchIfMissing = true)
public TaskScheduler configWatchTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
}
2.2 配置更新
ConfigWatch核心逻辑
- 继承了SmartLifecycle,会自动start,继承ApplicationEventPublisherAware获得应用publisher
- 在start中启动定时任务,默认1秒执行一次,可配置修改
- watchConfigKeyValues逻辑,对所有配置项
- 调用consul接口请求配置数据
- 如果有配置更新使用publisher发布RefreshEvent,下游进行数据更新
- 缓存结果
public class ConfigWatch implements ApplicationEventPublisherAware, SmartLifecycle {
//code removed
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void start() {
if (this.running.compareAndSet(false, true)) {
this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
this::watchConfigKeyValues, this.properties.getWatch().getDelay());
}
}
@Timed("consul.watch-config-keys")
public void watchConfigKeyValues() {
if (this.running.get()) {
for (String context : this.consulIndexes.keySet()) {
// turn the context into a Consul folder path (unless our config format
// are FILES)
if (this.properties.getFormat() != FILES && !context.endsWith("/")) {
context = context + "/";
}
try {
Long currentIndex = this.consulIndexes.get(context);
if (currentIndex == null) {
currentIndex = -1L;
}
// use the consul ACL token if found
String aclToken = this.properties.getAclToken();
if (StringUtils.isEmpty(aclToken)) {
aclToken = null;
}
Response<List<GetValue>> response = this.consul.getKVValues(context,
aclToken,
new QueryParams(this.properties.getWatch().getWaitTime(),
currentIndex));
// if response.value == null, response was a 404, otherwise it was a
// 200
// reducing churn if there wasn't anything
if (response.getValue() != null && !response.getValue().isEmpty()) {
Long newIndex = response.getConsulIndex();
if (newIndex != null && !newIndex.equals(currentIndex)) {
// don't publish the same index again, don't publish the first
// time (-1) so index can be primed
if (!this.consulIndexes.containsValue(newIndex)
&& !currentIndex.equals(-1L)) {
log.trace("Context " + context + " has new index "
+ newIndex);
RefreshEventData data = new RefreshEventData(context,
currentIndex, newIndex);
this.publisher.publishEvent(
new RefreshEvent(this, data, data.toString()));
}
this.consulIndexes.put(context, newIndex);
}
}
}
catch (Exception e) {
//异常处理
}
}
}
this.firstTime = false;
}
2.3 如何找到配置文件
参见org.springframework.cloud.consul.config.ConsulPropertySourceLocator
- 对每个配置的环境进行循环检查
- 构造配置文件路径,对一个key检查yml,yaml,properties几种
- 对每种路径调用consul API进行读取,读取到则进行解析并更新ConsulConfigProperties
@Retryable(interceptor = "consulRetryInterceptor")
public PropertySource<?> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
String appName = this.properties.getName();
if (appName == null) {
appName = env.getProperty("spring.application.name");
}
List<String> profiles = Arrays.asList(env.getActiveProfiles());
String prefix = this.properties.getPrefix();
List<String> suffixes = new ArrayList<>();
if (this.properties.getFormat() != FILES) {
suffixes.add("/");
}
else {
suffixes.add(".yml");
suffixes.add(".yaml");
suffixes.add(".properties");
}
String defaultContext = getContext(prefix,
this.properties.getDefaultContext());
for (String suffix : suffixes) {
this.contexts.add(defaultContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(this.contexts, defaultContext, profiles, suffix);
}
String baseContext = getContext(prefix, appName);
for (String suffix : suffixes) {
this.contexts.add(baseContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(this.contexts, baseContext, profiles, suffix);
}
Collections.reverse(this.contexts);
CompositePropertySource composite = new CompositePropertySource("consul");
for (String propertySourceContext : this.contexts) {
try {
ConsulPropertySource propertySource = null;
if (this.properties.getFormat() == FILES) {
Response<GetValue> response = this.consul.getKVValue(
propertySourceContext, this.properties.getAclToken());
addIndex(propertySourceContext, response.getConsulIndex());
if (response.getValue() != null) {
ConsulFilesPropertySource filesPropertySource = new ConsulFilesPropertySource(
propertySourceContext, this.consul, this.properties);
filesPropertySource.init(response.getValue());
propertySource = filesPropertySource;
}
}
else {
propertySource = create(propertySourceContext, this.contextIndex);
}
if (propertySource != null) {
composite.addPropertySource(propertySource);
}
}
catch (Exception e) {
//code removed
}
}
return composite;
}
return null;
}