本文章将经过结合consul config来讲解在springboot中怎样加载长途装备:经过consul config加载consul server中存储的装备。
咱们先来说下在spring中常规的加载装备文件的办法。
加载装备文件办法
关于一个工程来说,咱们一般都会需求有各种装备,在spring工程里边,一般都是yml或许properties文件,如下所示:
server:
port: 9991 # 端口
spring:
application:
name: ts-service1 # 运用称号
profiles:
active: dev # 指定环境,默许加载 default 环境
cloud:
consul:
# Consul 服务器地址
host: 51.6.196.200
port: 8500
# 装备中心相关装备
config:
# 是否启用装备中心,默许值 true 敞开
enabled: true
# 设置装备的根本文件夹,默许值 config 能够理解为装备文件地点的最外层文件夹
prefix: config
# 设置运用的文件夹称号,默许值 application 一般主张设置为微服务运用称号
default-context: tsService
# 装备环境分隔符,默许值 "," 和 default-context 装备项调配
# 例如运用 orderService 别离有环境 default、dev、test、prod
# 只需在 config 文件夹下创立 orderService、orderService-dev、orderService-test、orderService-prod 文件夹即可
profile-separator: '-'
# 指定装备格局为 yaml
format: YAML
# Consul 的 Key/Values 中的 Key,Value 对应整个装备文件
data-key: redisConfig
# 以上装备能够理解为:加载 config/orderService/ 文件夹下 Key 为 orderServiceConfig 的 Value 对应的装备信息
watch:
# 是否敞开主动改写,默许值 true 敞开
enabled: true
# 改写频率,单位:毫秒,默许值 1000
delay: 1000
# 服务发现相关装备
discovery:
register: true # 是否需求注册
instance-id: ${spring.application.name}-01 # 注册实例 id(有必要仅有)
service-name: ${spring.application.name} # 服务称号
port: ${server.port} # 服务端口
prefer-ip-address: true # 是否运用 ip 地址注册
ip-address: ${spring.cloud.client.ip-address} # 服务恳求 ip
那么关于读取这些装备文件中的值,一般有如下几种办法
- 1、运用 @Value(“${property}”) 读取比较简单的装备信息。
- 2、经过@ConfigurationProperties读取并经过@Component与 bean 绑定
- 3、经过@ConfigurationProperties读取并在运用的地方运用@EnableConfigurationProperties注册需求的装备 bean
- 4、经过@PropertySource读取指定 properties 文件
注:spring加载装备文件有个默许的加载顺序的,依据寄存的途径来决议。
拉取长途装备
咱们知道,上面说的那些一般要求装备都有必要是本地,而且格局只能是 properties(或许 yaml)。那么,假如咱们有长途装备,怎样把他引进进来来呢。首要有以下三步:
- 1、编写PropertySource:编写一个类承继EnumerablePropertySource,然后完成它的抽象办法即可。
- 2、编写PropertySourceLocator:PropertySourceLocator 其实便是用来定位咱们前面的PropertySource,需求重写的办法只要一个,便是回来一个PropertySource目标。
- 3、装备PropertySourceLocator收效
下面就以consul为例,剖析下它是怎样做的
consul的装备示例如下:
spring:
application:
name: ts-service1 # 运用称号
profiles:
active: dev # 指定环境,默许加载 default 环境
cloud:
consul:
# Consul 服务器地址
host: 51.6.196.200
port: 8500
# 装备中心相关装备
config:
# 是否启用装备中心,默许值 true 敞开
enabled: true
# 设置装备的根本文件夹,默许值 config 能够理解为装备文件地点的最外层文件夹
prefix: config
# 设置运用的文件夹称号,默许值 application 一般主张设置为微服务运用称号
default-context: tsService
# 装备环境分隔符,默许值 "," 和 default-context 装备项调配
# 例如运用 orderService 别离有环境 default、dev、test、prod
# 只需在 config 文件夹下创立 orderService、orderService-dev、orderService-test、orderService-prod 文件夹即可
profile-separator: '-'
# 指定装备格局为 yaml
format: YAML
# Consul 的 Key/Values 中的 Key,Value 对应整个装备文件
data-key: redisConfig
# 以上装备能够理解为:加载 config/orderService/ 文件夹下 Key 为 orderServiceConfig 的 Value 对应的装备信息
watch:
# 是否敞开主动改写,默许值 true 敞开
enabled: true
# 改写频率,单位:毫秒,默许值 1000
delay: 1000
一般关于装备文件,都有一个对应的装备属性类,consul也不例外:
@ConfigurationProperties("spring.cloud.consul.config")
@Validated
public class ConsulConfigProperties {
private boolean enabled = true;
private String prefix = "config";
@NotEmpty
private String defaultContext = "application";
@NotEmpty
private String profileSeparator = ",";
@NotNull
private Format format = Format.KEY_VALUE;
/**
* If format is Format.PROPERTIES or Format.YAML then the following field is used as
* key to look up consul for configuration.
*/
@NotEmpty
private String dataKey = "data";
@Value("${consul.token:${CONSUL_TOKEN:${spring.cloud.consul.token:${SPRING_CLOUD_CONSUL_TOKEN:}}}}")
private String aclToken;
private Watch watch = new Watch();
...
}
上面代码对应的便是yml文件中***spring.consul.config
***下面的这一部分的装备。
1、编写PropertySource
consul config下面有这样一个类:ConsulPropertySource
,看下它的承继关系:
public class ConsulPropertySource extends EnumerablePropertySource<ConsulClient> {
private final Map<String, Object> properties = new LinkedHashMap<>();
}
能够看到,它运用了一个map来说存储装备数据。
首要看下以下三个办法:
@Override
public Object getProperty(String name) {
return this.properties.get(name);
}
@Override
public String[] getPropertyNames() {
Set<String> strings = this.properties.keySet();
return strings.toArray(new String[strings.size()]);
}
这两个办法便是需求承继完成的父类办法,首要便是获取装备信息,那这些装备信息是哪里来的呢?下面看第三个办法:
public void init() {
if (!this.context.endsWith("/")) {
this.context = this.context + "/";
}
Response<List<GetValue>> response = this.source.getKVValues(this.context,
this.configProperties.getAclToken(), QueryParams.DEFAULT);
this.initialIndex = response.getConsulIndex();
final List<GetValue> values = response.getValue();
ConsulConfigProperties.Format format = this.configProperties.getFormat();
switch (format) {
case KEY_VALUE:
parsePropertiesInKeyValueFormat(values);
break;
case PROPERTIES:
case YAML:
parsePropertiesWithNonKeyValueFormat(values, format);
}
}
这儿的this.context能够理解为consul中key-value存储里边的key。this.source便是ConsulClient。
上面代码的逻辑便是:
- 经过consulclient向consul server发起恳求,查询前缀key为this.context的value信息
- 依据consul装备文件(也便是工程里边的yml或许properties文件)里边装备的format装备来决议解析该response
- 假如format是key-value,则表示consule server中该this.context对应的value是一个key-value格局的值,依照key-value进行解析放入this.properties中
- 假如format是yml或许properties,则表示consule server中该this.context对应的value是一个yml或许properties格局的值,依照相应的格局进行解析放入this.properties中
注:consul config还提供了另外一个propertySource的完成:
public class ConsulFilesPropertySource extends ConsulPropertySource {
public void init(GetValue value) {
if (this.getContext().endsWith(".yml") || this.getContext().endsWith(".yaml")) {
parseValue(value, YAML);
}
else if (this.getContext().endsWith(".properties")) {
parseValue(value, PROPERTIES);
}
else {
throw new IllegalStateException(
"Unknown files extension for context " + this.getContext());
}
}
}
该类承继自上面说的那个类,完成了init办法:首要便是用于直接将获取到的value依据需求解析成yml或许properties格局的数据。
2、编写PropertySourceLocator
consul config的完成类如下:
@Order(0)
public class ConsulPropertySourceLocator implements PropertySourceLocator {}
如上面所说,咱们首要关注下locate办法:
@Override
@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) {
if (this.properties.isFailFast()) {
log.error(
"Fail fast is set and there was an error reading configuration from consul.");
ReflectionUtils.rethrowRuntimeException(e);
}
else {
log.warn("Unable to load consul config from "
+ propertySourceContext, e);
}
}
}
return composite;
}
return null;
}
上面代码的逻辑便是:
- 经过上面装备文件中prefix和default-context、prefix和application.name经过分隔符组合成consule中的key
- 对每一个key,创立
ConsulPropertySource
实例并初始化(上一节咱们已经分析过了),将该实例保存下来
3、装备发动加载
consul config完成这样一个类:
@Configuration(proxyBeanMethods = false)
@ConditionalOnConsulEnabled
public class ConsulConfigBootstrapConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import(ConsulAutoConfiguration.class)
@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled",
matchIfMissing = true)
protected static class ConsulPropertySourceConfiguration {
@Autowired
private ConsulClient consul;
@Bean
@ConditionalOnMissingBean
public ConsulConfigProperties consulConfigProperties() {
return new ConsulConfigProperties();
}
@Bean
public ConsulPropertySourceLocator consulPropertySourceLocator(
ConsulConfigProperties consulConfigProperties) {
return new ConsulPropertySourceLocator(this.consul, consulConfigProperties);
}
}
}
然后在META-INF/spring.factories
中装备如下:
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.config.ConsulConfigAutoConfiguration
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration
便是给Spring Boot说,这个是一个发动装备类,spring boot在发动的时分会主动加载。
放入environment
上面讲解了对应接口的完成,那么consul的这些完成类是在哪里调用的呢?
进程是这样的:spring boot工程在发动的时分,会执行BootStrapConfiguration的initize办法,PropertySourceBootstrapConfiguration
的该办法如下:
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
Collection<PropertySource<?>> source = locator.locateCollection(environment);
if (source == null || source.size() == 0) {
continue;
}
List<PropertySource<?>> sourceList = new ArrayList<>();
for (PropertySource<?> p : source) {
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
sourceList.add(new BootstrapPropertySource<>(enumerable));
}
else {
sourceList.add(new SimpleBootstrapPropertySource(p));
}
}
logger.info("Located property source: " + sourceList);
composite.addAll(sourceList);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
for (PropertySource<?> p : environment.getPropertySources()) {
if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(p.getName());
}
}
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
能够看到,这儿代码中,调用了每个PropertySourceLocator
的实例办法locateCollection
,该办法里边调用了locate
办法,也便是回到了上一节所说的内容了。最终将所有的source放入了environment
中:insertPropertySources(propertySources, composite);
读取propertySource
经过上面的办法加载了长途装备之后,咱们在其他地方就能够任意读取了,办法如下:
//能够获取整个体系所有的装备:经过这个能够获取到更新之前的数据
ConfigurableEnvironment environment = (ConfigurableEnvironment)context.getEnvironment();
PropertySources sources = environment.getPropertySources();