通常,我们会使用配置文件来管理应用的配置。如一个 Spring Boot 的应用,可以将配置信息放在 application.yml 文件中,如果需要多环境配置,可以设置多个 application-{profile}.yml,再通过 spring.profiles.active={profile} 来实现多环境的切换。这样的管理对于单体应用或者说划分的服务不多的情况下没什么问题,但如果是一个微服务架构的应用系统有着很多个微服务,集中管理配置就非常必要了
对于这样的配置管理,我们会希望它
Spring Cloud Config 为分布式系统外部化配置提供了服务器端和客户端的支持,分为 Config Server 和 Config Client
Config Server 是用来集中管理应用程序的各个环境下的配置,默认是使用 Git 来存储配置内容的,可以很方便的对配置实现版本管理(也支持 Subversion 和本地化文件系统存储)
Config Client 即用来获取 Config Server 中存储的配置内容
创建一个 Spring Boot 工程,maven 添加 spring-cloud-config-server
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.4.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
在程序启动类上添加注解 @EnableConfigServer
,开启配置服务器的功能
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
Config Server 默认是使用 Git 来存储配置内容的,下面来配置下 git 仓库的相关信息
application.yml
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
# git 仓库地址
uri: https://github.com/Morgan412/weixin-order
# 仓库下的搜索路径
search-paths: conf
# git仓库账号
username:
# git仓库密码
password:
如果是公开仓库可以不用账号和密码,在仓库的 conf/
目录下建立如下的配置文件
每个文件中分别填入如下配置内容
Config Server 的端点
使用 Config Server 的端点可以获取配置文件的内容,请求地址与配置文件的映射如下:
上面的地址都可以映射到 {application}-{profile}.properties/yml
配置文件,{label}
表示对应 Git 仓库的分支,默认是 master
启动上面的 config-server 项目,通过 url 访问远程 Git 仓库 master 分支下 conf 目录下的 product-dev.yml
配置文件内容
通过 http://localhost:8888/product/dev 访问,可以获得应用名称、profile、git label、git version、配置文件URL、配置内容等信息
{
"name": "product",
"profiles": [
"dev"
],
"label": null,
"version": "052661b72043aad390e6774666b5594d6e0ba116",
"state": null,
"propertySources": [
{
"name": "https://github.com/Morgan412/weixin-order/conf/product-dev.yml",
"source": {
"profile": "dev"
}
},
{
"name": "https://github.com/Morgan412/weixin-order/conf/product.yml",
"source": {
"profile": "default"
}
}
]
}
而 http://localhost:8888/product-dev.yml 和 http://localhost:8888/product-dev.properties 只会获取到配置文件中的属性,包括 {application}.yml/properties
中的属性
上面创建了一个 Config Server 来集中管理配置,那下面来配置 Config Client 让微服务获取配置信息
同样是创建一个 Spring Boot 工程,添加 Config Client 的依赖,这里省略了其他依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
相关配置如下
server:
port: 8082
spring:
application:
# 对应 config server 所获取的配置文件 {application}
name: product
cloud:
config:
# 指定 config server 的地址,默认是 http://localhost:8888
uri: http://localhost:8888
# 对应配置文件的 {profile}
profile: dev
# 分支
label: master
上面的配置可以从 /{application}/{profile}/{label}
获取对应 config server 中的配置文件中的属性,其中
另外这些都可以通过设置 spring.cloud.config.*
(*
为 name
、profile
、label
) 来覆盖
如果这时就启动该服务,就可能会出现一些问题,因为上面关于 config client 的配置需要放到 bootstrap.yml
而不是 application.yml
。
我们来想一个问题,我们交给 config server 集中管理的配置内容是不是原本应该放在 application.yml
中在程序启动的时候被加载的,而现在我们把获取配置内容的 config client 相关配置放在了 application.yml
中,这样是不是就有点不对了,因为这里会出现一个先后顺序的问题。如果我们把配置放在了 application.yml
中,那么它会先去加载 bootstrap.yml
的配置属性(如果没有,会加载默认配置),假如我们在 application.yml
中配置的 uri 端口是 8080,那么它将不会被应用,还是默认的 8888 端口
Spring Cloud 有一个 引导上下文 的概念,它是主应用程序的父上下文,这个引导上下文负责从外部源(配置服务器,如Config Server)加载配置属性,及解密外部配置文件中的属性。主应用程序加载的是 application.(properties/yml) 中的属性,引导上下文加载 bootstrap.(properties/yml) 中的属性。这两个上下文共享一个
Environment
,但配置在 boostrap.* 中的属性有更高的优先级,因此默认情况下不能被本地配置覆盖。设置
spring.cloud.bootstrap.enabled=false
可以禁用引导过程
可以来写个 Controller 来测试一下获取配置
@RestController
class ConfigClientController {
@Value("${profile}")
private String profile;
@RequestMapping("/profile")
public String getProfile() {
return this.profile;
}
}