Spring Cloud配置中心

  1. Environment 抽象
  2. 基于git实现配置中心

Spring Cloud配置中心

版本信息

Spring Cloud : Hoxton.SR1

Spring Boot : 2.2.2.RELEASE

Environment 抽象

  • org.springframework.core.env.Environment 关注于读取profile
    • org.springframework.core.env.PropertyResolver 关注于读取配置
  • org.springframework.core.env.ConfigurableEnvironment 关注于存储profile
    • org.springframework.core.env.ConfigurablePropertyResolver 关注于类型转换

默认实现:org.springframework.core.env.StandardEnvironment

配置资源

  • org.springframework.core.env.PropertySource 单个读资源的抽象,对应注解@org.springframework.context.annotation.PropertySource
  • org.springframework.core.env.PropertySources 多个读资源的抽象,对应注解@org.springframework.context.annotation.PropertySource
  • org.springframework.core.env.MutablePropertySources 关注于存储资源

默认实现:org.springframework.core.env.CompositePropertySource

Spring Boot 外部化配置

  1. Devtools global settings properties on your home directory (~/.spring-bootdevtools.
    properties when devtools is active).

  2. @TestPropertySource annotations on your tests.

  3. properties attribute on your tests. Available on @SpringBootTest and the test annotations for
    testing a particular slice of your application.

  4. Command line arguments.

    commandLineArgs –>org.springframework.core.env.CommandLinePropertySource

  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable
    or system property).

spring.application.json –>org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor.JsonPropertySource

  1. ServletConfig init parameters.

    servletConfigInitParams –> org.springframework.core.env.PropertySource.StubPropertySource 占位

    –>org.springframework.web.context.support.ServletConfigPropertySource

  2. ServletContext init parameters.

    servletContextInitParams –>org.springframework.core.env.PropertySource.StubPropertySource 占位

    –> org.springframework.web.context.support.ServletContextPropertySource

  3. JNDI attributes from java:comp/env.

    jndiProperties –>org.springframework.jndi.JndiPropertySource

  4. Java System properties (System.getProperties()).

systemProperties –>org.springframework.core.env.PropertiesPropertySource

  1. OS environment variables.

systemEnvironment–>org.springframework.core.env.SystemEnvironmentPropertySource

  1. A RandomValuePropertySource that has properties only in random.*.

random –> org.springframework.boot.env.RandomValuePropertySource

  1. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).

  2. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).

  3. Application properties outside of your packaged jar (application.properties and YAML variants).

  4. Application properties packaged inside your jar (application.properties and YAML variants).

12-15类型applicationConfig: [${location"] –>org.springframework.boot.env.OriginTrackedMapPropertySource

  1. @PropertySource annotations on your @Configuration classes.

  2. Default properties (specified by setting SpringApplication.setDefaultProperties).

类型转换

  • org.springframework.core.convert.ConversionService 类型转换服务
  • org.springframework.core.convert.converter.Converter 类型转换接口
  • org.springframework.core.convert.converter.GenericConverter 普通的类型转换接口
  • org.springframework.core.convert.converter.ConditionalConverter 适配(条件)的类型转换
  • org.springframework.core.convert.converter.ConverterRegistry 类型转换的注册中心
  • org.springframework.core.convert.converter.ConverterFactory 类型转的注册工厂
  • org.springframework.core.convert.support.ConfigurableConversionService 可配置的类型转换服务

默认实现:org.springframework.core.convert.support.DefaultConversionService

配置文件加载

  • org.springframework.boot.env.PropertySourceLoader 配置资源加载
    • org.springframework.boot.env.PropertiesPropertySourceLoader properties(xml)配置文件加载
    • org.springframework.boot.env.YamlPropertySourceLoader yml(yaml)配置文件加载
  • org.springframework.cloud.bootstrap.config.PropertySourceLocator Spring Cloud中配置资源加载
    • org.springframework.cloud.config.client.ConfigServicePropertySourceLocator 配置服务资源实现
    • org.springframework.cloud.config.server.environment.EnvironmentRepositoryPropertySourceLocator 环境仓库配置服务资源实现

Environment与PropertySource的关系

Spring

  • ``org.springframework.core.env.Environment`
    • org.springframework.core.env.ConfigurableEnvironment
      • org.springframework.core.env.MutablePropertySources
        • org.springframework.core.env.PropertySource

Spring Cloud

  • org.springframework.cloud.config.environment.Environment
    • org.springframework.cloud.config.environment.PropertySource

基于git实现配置中心

服务端

应用

  1. 创建git的配置目录文件,以E:/GitHub/properties文件夹为例,使用的是git客户端

    1
    2
    cd /e/GitHub/properties
    git init
  2. 创建配置文件configserver.yml以及configserver-dev.yml,配置文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    management:
    endpoints:
    web:
    exposure:
    include: health,info,beans,env
    spring:
    application:
    name: application-name

    配置文件命名规则:

    {application}/{profile}[/{label}]
    {application}-{profile}.yml
    {label}/{application}-{profile}.yml
    {application}-{profile}.properties
    {label}/{application}-{profile}.properties

    • {application} 为配置文件名称,对应spring.cloud.config.name
    • {profile} 为配置文件激活环境,对应spring.cloud.config.profile,没有默认为default
    • {label} 为配置文件标签(git中为分支版本),对应spring.cloud.config.label
  3. 添加pom的依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
  4. 启动类添加@EnableConfigServer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 配置服务启动类
    *
    * @author FelixFly <chenglinxu@yeah.net>
    * @date 2020/1/29
    */
    @SpringBootApplication
    @EnableConfigServer
    public class SpringCloudConfigServerApplication {

    public static void main(String[] args) {
    SpringApplication.run(SpringCloudConfigServerApplication.class, args);
    }
    }
  5. 配置文件bootstrap.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    management:
    endpoints:
    web:
    exposure:
    include: health,info,beans,env
    server:
    port: 8090
    spring:
    cloud:
    config:
    server:
    git:
    uri: file:///E:/GitHub/properties
  6. 启动SpringCloudConfigServerApplication,验证配置文件访问路径为http://127.0.0.1:8090/{application}/{profile},若是有{label},后面再加上/{label}

    {label}若是存在”/“,使用“(_)”进行替换,实现类org.springframework.cloud.config.server.environment.EnvironmentController

    • 示例访问地址:http://127.0.0.1:8090/configserver/default

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      {
      "name": "configserver",
      "profiles": [
      "default"
      ],
      "label": null,
      "version": "f6cb3fcb36ae0ea085d0342ebe5e68811b9098a5",
      "state": null,
      "propertySources": [{
      "name": "file:///E:/GitHub/properties/configserver.yml",
      "source": {
      "management.endpoints.web.exposure.include": "health,info,beans,env",
      "spring.application.name": "application-name"
      }
      }
      ]
      }
    • 示例访问地址:http://127.0.0.1:8090/configserver/dev

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      {
      "name": "configserver",
      "profiles": [
      "dev"
      ],
      "label": null,
      "version": "f6cb3fcb36ae0ea085d0342ebe5e68811b9098a5",
      "state": null,
      "propertySources": [{
      "name": "file:///E:/GitHub/properties/configserver-dev.yml",
      "source": {
      "management.endpoints.web.exposure.include": "health,info,beans,env",
      "spring.application.name": "application-name"
      }
      }, {
      "name": "file:///E:/GitHub/properties/configserver.yml",
      "source": {
      "management.endpoints.web.exposure.include": "health,info,beans,env",
      "spring.application.name": "application-name"
      }
      }
      ]
      }

源码分析

  1. @EnableConfigServer注解

    1
    2
    3
    4
    5
    6
    7
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(ConfigServerConfiguration.class)
    public @interface EnableConfigServer {

    }

    就是导入了ConfigServerConfiguration的配置Bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration(proxyBeanMethods = false)
    public class ConfigServerConfiguration {

    @Bean
    public Marker enableConfigServerMarker() {
    return new Marker();
    }

    class Marker {

    }
    }

    这个ConfigServerConfiguration配置Bean就配置了一个Marker的Bean,猜想这个Marker的Bean是不是某个配置的自动装配条件

  2. ConfigServerAutoConfiguration 服务配置自动装配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnBean(ConfigServerConfiguration.Marker.class)
    @EnableConfigurationProperties(ConfigServerProperties.class)
    @Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class,
    ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class,
    ConfigServerMvcConfiguration.class, ResourceEncryptorConfiguration.class })
    public class ConfigServerAutoConfiguration {

    }

    这个ConfigServerAutoConfiguration自动装配条件是ConfigServerConfiguration.Marker.class这个类型的Bean存在

    • 启用了ConfigServerProperties配置信息
    • 导入了一些配置信息,如EnvironmentRepositoryConfiguration
  3. 默认启用git的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(value = EnvironmentRepository.class,
search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {

@Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
return gitEnvironmentRepositoryFactory.build(environmentProperties);
}

}

DefaultRepositoryConfiguration在不存在EnvironmentRepository.class类型的Bean下自动装配,MultipleJGitEnvironmentRepository–>JGitEnvironmentRepository git仓储的实现

  1. 自定义环境仓储的实现

    从上面可得知,默认的git环境仓储实现只有在不存在EnvironmentRepository.class类型的Bean下自动装配,我们只需要自定义一个EnvironmentRepository的Bean就行了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Bean
    public EnvironmentRepository defaultEnvironmentRepository(){
    return (application, profile, label) -> {
    Environment environment = new Environment("custom-default");
    List<PropertySource> propertySources = environment.getPropertySources();
    Map<String, String> sourceMap = new HashMap<>();
    sourceMap.put("application", "custom-application");
    PropertySource propertySource = new PropertySource("map", sourceMap);
    propertySources.add(propertySource);
    return environment;
    };
    }

    访问地址:http://127.0.0.1:8090/{application}/{profile},若是有{label},后面再加上/{label}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "name": "config",// {application}
    "profiles": [
    "default" // {profile}
    ],
    "label": null, // {label}
    "version": null,
    "state": null,
    "propertySources": [{
    "name": "map",
    "source": {
    "application": "custom-application"
    }
    }
    ]
    }

客户端

应用

  1. 添加pom的依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
  2. 配置文件bootstrap.yml

    1
    2
    3
    4
    5
    6
    7
    spring:
    cloud:
    config:
    uri: http://127.0.0.1:8090
    name: configserver
    application:
    name: config-client
  3. 服务启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * 配置客户端启动类
    *
    * @author FelixFly <chenglinxu@yeah.net>
    * @date 2020/1/29
    */
    @SpringBootApplication
    public class SpringCloudConfigClientApplication {

    public static void main(String[] args) {
    SpringApplication.run(SpringCloudConfigClientApplication.class, args);
    }
    }
  4. 启动服务,查看/actuator/env

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    {
    "activeProfiles": [],
    "propertySources": [{
    "name": "server.ports",
    "properties": {}
    }, {
    "name": "bootstrapProperties-configClient",
    "properties": {}
    }, {
    "name": "bootstrapProperties-file:///E:/GitHub/properties/configserver.yml",
    "properties": {}
    }, {
    "name": "servletContextInitParams",
    "properties": {}
    }, {
    "name": "systemProperties",
    "properties": {}
    }, {
    "name": "systemEnvironment",
    "properties": {}
    }, {
    "name": "springCloudClientHostInfo",
    "properties": {}
    }, {
    "name": "applicationConfig: [classpath:/bootstrap.yml]",
    "properties": {}
    }, {
    "name": "springCloudDefaultProperties",
    "properties": {}
    }
    ]
    }

    从上得知配置信息bootstrapProperties-file:///E:/GitHub/properties/configserver.yml具有高优先级,从先前的知识得知,自定义的bootstrap具有高优先级,实现类实现了PropertySourceLocator

    自定义配置加载类org.springframework.cloud.config.client.ConfigServicePropertySourceLocator

源码分析

ConfigServicePropertySourceLocator加载配置资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**
* @author Dave Syer
* @author Mathieu Ouellet
*
*/
@Order(0)
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {

private static Log logger = LogFactory
.getLog(ConfigServicePropertySourceLocator.class);

private RestTemplate restTemplate;

private ConfigClientProperties defaultProperties;

public ConfigServicePropertySourceLocator(ConfigClientProperties defaultProperties) {
this.defaultProperties = defaultProperties;
}

@Override
@Retryable(interceptor = "configServerRetryInterceptor")
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
// 从environment中覆盖spring.cloud.config相关配置(name|profile|label)
// 这个地方有个优先级的问题,若是environment已经存在spring.cloud.config的相关配置,就使用 environment的配置,不再使用本地spring.cloud.config的配置
ConfigClientProperties properties = this.defaultProperties.override(environment);
CompositePropertySource composite = new OriginTrackedCompositePropertySource(
"configService");
RestTemplate restTemplate = this.restTemplate == null
? getSecureRestTemplate(properties) : this.restTemplate;
Exception error = null;
String errorBody = null;
try {
String[] labels = new String[] { "" };
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 result = getRemoteEnvironment(restTemplate, properties,
label.trim(), state);
if (result != null) {
log(result);

// result.getPropertySources() can be null if using xml
if (result.getPropertySources() != null) {
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = translateOrigins(source.getName(),
(Map<String, Object>) source.getSource());
composite.addPropertySource(
new OriginTrackedMapPropertySource(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;
}
}
errorBody = String.format("None of labels %s found", Arrays.toString(labels));
}
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: "
+ (error != null ? error.getMessage() : errorBody));
return null;

}


...

private Environment getRemoteEnvironment(RestTemplate restTemplate,
ConfigClientProperties properties, String label, String state) {
String path = "/{name}/{profile}";
String name = properties.getName();
String profile = properties.getProfile();
String token = properties.getToken();
int noOfUrls = properties.getUri().length;
if (noOfUrls > 1) {
logger.info("Multiple Config Server Urls found listed.");
}

Object[] args = new String[] { name, profile };
if (StringUtils.hasText(label)) {
if (label.contains("/")) {
label = label.replace("/", "(_)");
}
args = new String[] { name, profile, label };
path = path + "/{label}";
}
ResponseEntity<Environment> response = null;

for (int i = 0; i < noOfUrls; i++) {
Credentials credentials = properties.getCredentials(i);
String uri = credentials.getUri();
String username = credentials.getUsername();
String password = credentials.getPassword();

logger.info("Fetching config from server at : " + uri);

try {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(
Collections.singletonList(MediaType.parseMediaType(V2_JSON)));
addAuthorizationToken(properties, headers, username, password);
if (StringUtils.hasText(token)) {
headers.add(TOKEN_HEADER, token);
}
if (StringUtils.hasText(state) && properties.isSendState()) {
headers.add(STATE_HEADER, state);
}

final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);
// 构建URL,通过restTemplate进行获取
// uri + path = uri/{application}/{profile}/{label}
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
Environment.class, args);
}
catch (HttpClientErrorException e) {
if (e.getStatusCode() != HttpStatus.NOT_FOUND) {
throw e;
}
}
catch (ResourceAccessException e) {
logger.info("Connect Timeout Exception on Url - " + uri
+ ". Will be trying the next url if available");
if (i == noOfUrls - 1) {
throw e;
}
else {
continue;
}
}

if (response == null || response.getStatusCode() != HttpStatus.OK) {
return null;
}

Environment result = response.getBody();
return result;
}

return null;
}

...

}

Spring Cloud配置中心
http://example.com/2020/01/29/SpringCloud/Spring Cloud配置中心/
作者
FelixFly
发布于
2020年1月29日
许可协议