文章目录
- 前言
- 一、Spring Cloud Config Service
- 引入POM文件
- 启动配置服务
- 基于(Native)本地配置
- 配置类NativeEnvironmentProperties
- 解析本地配置文件 NativeEnvironmentRepository
- 配置文件 application.yml
- 基于Git配置
- 配置说明
- 多Git URI配置
- 配置类的抽象类
- 解析Git存库配置类MultipleJGitEnvironmentRepository
- 配置文件 application.yml
- 基于JDBC配置
- 基于JDBC存储配置类JdbcEnvironmentProperties
- 解析JDBC配置类
- 配置文件application.yml
- 二、Spring Cloud Config Client
- 引入POM文件
- 配置文件bootstrap.yml
前言
通过统一配置服务,可以解决服务中配置文件分散,不利于管理。
一、Spring Cloud Config Service
引入POM文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
启动配置服务
@SpringBootApplication
//服务发现客户端
@EnableDiscoveryClient
//配置服务器端
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args){
SpringApplication.run(ConfigApplication.class,args);
}
}
基于(Native)本地配置
配置类NativeEnvironmentProperties
@ConfigurationProperties("spring.cloud.config.server.native")
public class NativeEnvironmentProperties implements EnvironmentRepositoryProperties {
/**
* 确定解密期间如何处理异常的标志(默认为false)
*/
private Boolean failOnError = false;
/**
* 用于确定是否应添加分支位置的标志
*/
private Boolean addLabelLocations = true;
/**
* 默认分支
*/
private String defaultLabel = "master";
/**
* 搜索配置文件路径,文件格式类似Spring Boot的配置文件.
* 本地文件配置如: [classpath:/,classpath:/config/,file:./,file:./config/].
*/
private String[] searchLocations = new String[0];
/**
* 本地仓库版本号
*/
private String version;
private int order = Ordered.LOWEST_PRECEDENCE;
解析本地配置文件 NativeEnvironmentRepository
重要代码如下
@Override
public Environment findOne(String config, String profile, String label,
boolean includeOrigin) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(
PropertyPlaceholderAutoConfiguration.class);
ConfigurableEnvironment environment = getEnvironment(profile);
builder.environment(environment);
builder.web(WebApplicationType.NONE).bannerMode(Mode.OFF);
if (!logger.isDebugEnabled()) {
// Make the mini-application startup less verbose
builder.logStartupInfo(false);
}
String[] args = getArgs(config, profile, label);
// Explicitly set the listeners (to exclude logging listener which would change
// log levels in the caller)
builder.application()
.setListeners(Arrays.asList(new ConfigFileApplicationListener()));
try (ConfigurableApplicationContext context = builder.run(args)) {
environment.getPropertySources().remove("profiles");
return clean(new PassthruEnvironmentRepository(environment).findOne(config,
profile, label, includeOrigin));
}
catch (Exception e) {
String msg = String.format(
"Could not construct context for config=%s profile=%s label=%s includeOrigin=%b",
config, profile, label, includeOrigin);
String completeMessage = NestedExceptionUtils.buildMessage(msg,
NestedExceptionUtils.getMostSpecificCause(e));
throw new FailedToConstructEnvironmentException(completeMessage, e);
}
}
@Override
public Locations getLocations(String application, String profile, String label) {
String[] locations = this.searchLocations;
if (this.searchLocations == null || this.searchLocations.length == 0) {
locations = DEFAULT_LOCATIONS;
}
Collection<String> output = new LinkedHashSet<String>();
if (label == null) {
label = this.defaultLabel;
}
for (String location : locations) {
String[] profiles = new String[] { profile };
if (profile != null) {
profiles = StringUtils.commaDelimitedListToStringArray(profile);
}
String[] apps = new String[] { application };
if (application != null) {
apps = StringUtils.commaDelimitedListToStringArray(application);
}
for (String prof : profiles) {
for (String app : apps) {
String value = location;
if (application != null) {
value = value.replace("{application}", app);
}
if (prof != null) {
value = value.replace("{profile}", prof);
}
if (label != null) {
value = value.replace("{label}", label);
}
if (!value.endsWith("/")) {
value = value + "/";
}
if (isDirectory(value)) {
output.add(value);
}
}
}
}
if (this.addLabelLocations) {
for (String location : locations) {
if (StringUtils.hasText(label)) {
String labelled = location + label.trim() + "/";
if (isDirectory(labelled)) {
output.add(labelled);
}
}
}
}
return new Locations(application, profile, label, this.version,
output.toArray(new String[0]));
}
private ConfigurableEnvironment getEnvironment(String profile) {
ConfigurableEnvironment environment = new StandardEnvironment();
Map<String, Object> map = new HashMap<>();
map.put("spring.profiles.active", profile);
map.put("spring.main.web-application-type", "none");
environment.getPropertySources().addFirst(new MapPropertySource("profiles", map));
return environment;
}
protected Environment clean(Environment value) {
Environment result = new Environment(value.getName(), value.getProfiles(),
value.getLabel(), this.version, value.getState());
for (PropertySource source : value.getPropertySources()) {
String name = source.getName();
if (this.environment.getPropertySources().contains(name)) {
continue;
}
name = name.replace("applicationConfig: [", "");
name = name.replace("]", "");
if (this.searchLocations != null) {
boolean matches = false;
String normal = name;
if (normal.startsWith("file:")) {
normal = StringUtils
.cleanPath(new File(normal.substring("file:".length()))
.getAbsolutePath());
}
String profile = result.getProfiles() == null ? null
: StringUtils.arrayToCommaDelimitedString(result.getProfiles());
for (String pattern : getLocations(result.getName(), profile,
result.getLabel()).getLocations()) {
if (!pattern.contains(":")) {
pattern = "file:" + pattern;
}
if (pattern.startsWith("file:")) {
pattern = StringUtils
.cleanPath(new File(pattern.substring("file:".length()))
.getAbsolutePath())
+ "/";
}
if (logger.isTraceEnabled()) {
logger.trace("Testing pattern: " + pattern
+ " with property source: " + name);
}
if (normal.startsWith(pattern)
&& !normal.substring(pattern.length()).contains("/")) {
matches = true;
break;
}
}
if (!matches) {
// Don't include this one: it wasn't matched by our search locations
if (logger.isDebugEnabled()) {
logger.debug("Not adding property source: " + name);
}
continue;
}
}
logger.info("Adding property source: " + name);
result.add(new PropertySource(name, source.getSource()));
}
return result;
}
private String[] getArgs(String application, String profile, String label) {
List<String> list = new ArrayList<String>();
String config = application;
if (!config.startsWith("application")) {
config = "application," + config;
}
list.add("--spring.config.name=" + config);
list.add("--spring.cloud.bootstrap.enabled=false");
list.add("--encrypt.failOnError=" + this.failOnError);
list.add("--spring.config.location=" + StringUtils.arrayToCommaDelimitedString(
getLocations(application, profile, label).getLocations()));
return list.toArray(new String[0]);
}
配置文件 application.yml
spring:
application:
name: config
profiles:
active: native
cloud:
config:
server:
native:
search-locations: classpath:/config/
# 默认分支 master
default-label: master
version: 1.0
# 默认值 {spring.application.name}
name: config
基于Git配置
配置说明
配置Key | 默认值 | 说明 |
uri | Git URI地址 | |
default-label | master | Git分支 |
timeout | 5 | 连接Git时,如果是HTTPS是否跳过SSL验证 |
skip-ssl-validation | false | 连接Git超时时间,默认5秒 |
privateKey | 有效的SSH私钥。如果ignoreLocalSshSettings为true且Git URI为,则必须设置SSH 格式. | |
ignore-local-ssh-settings | false | 如果为true,请使用基于属性的SSH配置,而不是基于文件的SSH配置。 |
host-key | 有效的SSH主机密钥。如果还设置了hostKeyAlgorithm,则必须设置 | |
host-key-algorithm | 算法如下一种: ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384,ecdsa-sha2-nistp521. 如果设置了hostKey,则必须要设置 | |
clone-on-start | false | 启动时是否克隆库(按需克隆)会导致第一次启动较慢,但是第一次查询较快. |
clone-submodules | false | 是否克隆存储库的子模块快. |
force-pull | false | 指示存储库应强制拉的标志。如果为true,则丢弃任何本地,更改和从远程存储库获取 |
多Git URI配置
uri:
为repos匹配不到时候,就会默认访问uri的资源
repos: 配置中的key,如果没有配置pattern则,pattern值为 'key/*'
pattern:匹配规则,${applicationName}/${profile}和pattern中的值匹配
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
repos:
development:
pattern:
- '*/development'
- '*/staging'
uri: https://github.com/development/config-repo
staging:
pattern:
- '*/qa'
- '*/production'
uri: https://github.com/staging/config-repo
配置类的抽象类
public class AbstractScmAccessorProperties implements EnvironmentRepositoryProperties {
static final String[] DEFAULT_LOCATIONS = new String[] { "/" };
/** 远程仓库URI. */
private String uri;
/** 存储库本地工作副本的基本目录. */
private File basedir;
/** 搜索要在本地工作副本中使用的路径。默认情况下,只搜索根 */
private String[] searchPaths = DEFAULT_LOCATIONS.clone();
/** 远程仓库认知用户名. */
private String username;
/** 远程仓库认证密码. */
private String password;
/** 解锁SSH私钥的口令. */
private String passphrase;
/** 拒绝来自不在已知主机列表中的远程服务器的传入SSH主机密钥 */
private boolean strictHostKeyChecking = true;
/** 环境存储库的顺序. */
private int order = Ordered.LOWEST_PRECEDENCE;
/** 与远程存储库一起使用的默认标签 */
private String defaultLabel;
解析Git存库配置类MultipleJGitEnvironmentRepository
重要代码
@Override
public Locations getLocations(String application, String profile, String label) {
for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
if (repository.matches(application, profile, label)) {
for (JGitEnvironmentRepository candidate : getRepositories(repository,
application, profile, label)) {
try {
Environment source = candidate.findOne(application, profile,
label, false);
if (source != null) {
return candidate.getLocations(application, profile, label);
}
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Cannot retrieve resource locations from "
+ candidate.getUri() + ", cause: ("
+ e.getClass().getSimpleName() + ") "
+ e.getMessage(), e);
}
continue;
}
}
}
}
JGitEnvironmentRepository candidate = getRepository(this, application, profile,
label);
if (candidate == this) {
return super.getLocations(application, profile, label);
}
return candidate.getLocations(application, profile, label);
}
@Override
public Environment findOne(String application, String profile, String label,
boolean includeOrigin) {
for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
if (repository.matches(application, profile, label)) {
for (JGitEnvironmentRepository candidate : getRepositories(repository,
application, profile, label)) {
try {
if (label == null) {
label = candidate.getDefaultLabel();
}
Environment source = candidate.findOne(application, profile,
label, includeOrigin);
if (source != null) {
return source;
}
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"Cannot load configuration from " + candidate.getUri()
+ ", cause: (" + e.getClass().getSimpleName()
+ ") " + e.getMessage(),
e);
}
continue;
}
}
}
}
JGitEnvironmentRepository candidate = getRepository(this, application, profile,
label);
if (label == null) {
label = candidate.getDefaultLabel();
}
if (candidate == this) {
return super.findOne(application, profile, label, includeOrigin);
}
return candidate.findOne(application, profile, label, includeOrigin);
}
private List<JGitEnvironmentRepository> getRepositories(
JGitEnvironmentRepository repository, String application, String profile,
String label) {
List<JGitEnvironmentRepository> list = new ArrayList<>();
String[] profiles = profile == null ? new String[] { null }
: StringUtils.commaDelimitedListToStringArray(profile);
for (int i = profiles.length; i-- > 0;) {
list.add(getRepository(repository, application, profiles[i], label));
}
return list;
}
JGitEnvironmentRepository getRepository(JGitEnvironmentRepository repository,
String application, String profile, String label) {
if (!repository.getUri().contains("{")) {
return repository;
}
String key = repository.getUri();
// cover the case where label is in the uri, but no label was sent with the
// request
if (key.contains("{label}") && label == null) {
label = repository.getDefaultLabel();
}
if (application != null) {
key = key.replace("{application}", application);
}
if (profile != null) {
key = key.replace("{profile}", profile);
}
if (label != null) {
key = key.replace("{label}", label);
}
if (!this.placeholders.containsKey(key)) {
this.placeholders.put(key, getRepository(repository, key));
}
return this.placeholders.get(key);
}
private JGitEnvironmentRepository getRepository(JGitEnvironmentRepository source,
String uri) {
JGitEnvironmentRepository repository = new JGitEnvironmentRepository(null,
new JGitEnvironmentProperties());
File basedir = repository.getBasedir();
BeanUtils.copyProperties(source, repository);
repository.setUri(uri);
repository.setBasedir(new File(source.getBasedir(), basedir.getName()));
return repository;
}
配置文件 application.yml
cloud:
config:
server:
git:
uri: git@gitee.com:swg/config.git
private-key: xxx
host-key-algorithm: ecdsa-sha2-nistp256
known-hosts-file: ~/.ssh/known_hosts
search-paths: cloud
label: master
基于JDBC配置
基于JDBC存储配置类JdbcEnvironmentProperties
@ConfigurationProperties("spring.cloud.config.server.jdbc")
public class JdbcEnvironmentProperties implements EnvironmentRepositoryProperties {
//默认查询配置语句
private static final String DEFAULT_SQL = "SELECT KEY, VALUE from PROPERTIES"
+ " where APPLICATION=? and PROFILE=? and LABEL=?";
/**
* 是否启动JDBC配置
*/
private boolean enabled = true;
private int order = Ordered.LOWEST_PRECEDENCE - 10;
/** 查询数据库中key和value的SQL */
private String sql = DEFAULT_SQL;
/**
* 查询失败报异常
*/
private boolean failOnError = true;
解析JDBC配置类
重要代码:在这里会将 key 和value的Result 转为 map
会有一个默认的共有配置: application: application, profile: default
@Override
public Environment findOne(String application, String profile, String label) {
String config = application;
if (StringUtils.isEmpty(label)) {
label = "master";
}
if (StringUtils.isEmpty(profile)) {
profile = "default";
}
if (!profile.startsWith("default")) {
profile = "default," + profile;
}
String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
Environment environment = new Environment(application, profiles, label, null,
null);
if (!config.startsWith("application")) {
config = "application," + config;
}
List<String> applications = new ArrayList<String>(new LinkedHashSet<>(
Arrays.asList(StringUtils.commaDelimitedListToStringArray(config))));
List<String> envs = new ArrayList<String>(
new LinkedHashSet<>(Arrays.asList(profiles)));
Collections.reverse(applications);
Collections.reverse(envs);
for (String app : applications) {
for (String env : envs) {
try {
Map<String, String> next = (Map<String, String>) this.jdbc.query(
this.sql, new Object[] { app, env, label }, this.extractor);
if (!next.isEmpty()) {
environment.add(new PropertySource(app + "-" + env, next));
}
}
catch (DataAccessException e) {
if (!failOnError) {
if (logger.isDebugEnabled()) {
logger.debug(
"Failed to retrieve configuration from JDBC Repository",
e);
}
}
else {
throw e;
}
}
}
}
return environment;
}
配置文件application.yml
spring:
application:
name: config
profiles:
active: native
cloud:
config:
server:
jdbc:
# 其中key和value 是可以随意字段只要含义相同即可
sql: SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?
二、Spring Cloud Config Client
引入POM文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
配置文件bootstrap.yml
spring:
application:
name: config
cloud:
config:
#
label: master
uri: http://localhost:9100
# 默认名称 ${spring.application.name}
name: custmoer
profile: dev