《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Spring-Cloud/Spring-Cloud-Config/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 概述

Spring Cloud Config 是由 Spring Cloud 官方推出,基于 Spring Cloud 体系的配置中心。

相比 NacosApollo 等其它配置中心来说,Spring Cloud Config 是一个轻量级的配置中心,和 Spring Cloud 的集成度会更好,不过功能上会薄弱一些。例如说,灰度发布、运维界面、配置回滚等等。因此,还是推荐使用 Apollo 或者 Nacos 嘿嘿~

Spring Cloud Config 整体架构如下图所示:架构图

一共分成 Spring Cloud Config ClientServer 两个角色:

  • Spring Cloud Config Server 支持从 Git、数据库、本地文件等多种存储器中,读取数据作为配置,并提供 HTTP API 给 Spring Cloud Config Client 使用。
  • Spring Cloud Config Client 在项目启动时,从 Spring Cloud Config Server 中读取到配置,并缓存在本地使用。

2. 快速入门

示例代码对应仓库:

本小节,我们来快速入门 Spring Cloud Config 的使用。

目前比较推荐使用 Git 作为 Spring Cloud Config Server 的存储器,因为 Git 支持配置的版本管理、可以使用 PR 流程实现配置审核、支持 Webhook 回调实现配置的动态刷新。因此,我们会创建 labx-12-sc-config-server-git 项目,搭建的使用 Git 作为存储器的 Spring Cloud Config Server

同时,我们会创建 labx-12-sc-config-user-application 项目,使用 Spring Cloud Config Client 读取配置。

最终项目如下图所示:项目结构

2.1 搭建 Spring Cloud Config Server

创建 labx-12-sc-config-server-git 项目,搭建的使用 Git 作为存储器的 Spring Cloud Config Server

2.1.1 创建 Git 仓库

① 创建一个 Git 仓库 demo-config-server ,作为 Spring Cloud Config Server 的 Git 存储器。如下图所示:demo-config-server 仓库

② 在仓库下,创建 user-application.yml 配置文件,如下图所示:

user-application.yml 配置文件

order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 120 # 订单创建频率,单位:秒

稍后,我们创建的用户服务 user-application,就会通过 Spring Cloud Config Server 提供的 HTTP 接口,读取到 user-application.yml 配置文件的配置。注意,这里名字要对应上噢,稍后艿艿会讲解具体原因~

2.1.2 引入依赖

下面,我们来正式搭建 Spring Cloud Config Server。首先在 pom.xml 文件中,引入 Spring Cloud Config Server 依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>labx-12</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>labx-12-sc-config-server-git</artifactId>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
</properties>

<!--
引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,进行依赖版本的管理,防止不兼容。
在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 开发团队推荐了三者的依赖关系
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 引入 Spring Cloud Config Server 相关依赖,实现配置中心的服务端,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>

</project>

通过 spring-cloud-config-server 依赖,引入 Spring Cloud Config Server 相关依赖,实现配置中心的服务端,并实现对其的自动配置。

2.1.3 配置文件

创建 application.yml 配置文件,引入 Spring Cloud Config Server 相关配置项。配置内容如下:

server:
port: 8888

spring:
application:
name: demo-config-server

profiles:
active: git # 使用的 Spring Cloud Config Server 的存储器方案
cloud:
config:
server:
# Spring Cloud Config Server 的 Git 存储器的配置项,对应 MultipleJGitEnvironmentProperties 类
git:
uri: https://github.com/YunaiV/demo-config-server.git # Git 仓库地址
search-paths: / # 读取文件的根地址
default-label: master # 使用的默认分支,默认为 master
# username: ${CODING_USERNAME} # 账号
# password: ${CODING_PASSWORD} # 密码

① 设置 spring.profiles.active 配置项为 git,设置使用的 Spring Cloud Config Server 的存储器方案为 Git。感兴趣的胖友,可以看看 EnvironmentRepositoryConfiguration 配置类,Spring Cloud Config Server 是基于 spring.profiles.active 配置项来选择使用的存储器方案,如下图所示:EnvironmentRepositoryConfiguration 配置类

spring.cloud.config.server 为 Spring Cloud Config Server 的配置项,对应 ConfigServerProperties 类。一般情况下,默认配置即可,因此本示例也是如此。

spring.cloud.config.server.git 为 Spring Cloud Config Server 的 Git 存储器的配置项,对应 MultipleJGitEnvironmentProperties 类。

  • uri:配置文件所在的 Git 仓库地址。
  • search-paths:配置文件所在的根目录。
  • default-label:使用的默认分支,默认为 master。在 Spring Cloud Config Client 读取 Spring Cloud Config Server 的配置时,如果 Client 未传递 label 请求参数,则默认读取 Server 设置的 default-label 默认分支。
  • usernamepassword 为账号密码,有需要的时候配置。

2.1.4 ConfigServerApplication

创建 ConfigServerApplication 类,作为 Spring Cloud Config Server 的启动类。代码如下:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {

public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}

}

通过 @EnableConfigServer 注解,声明启动 Spring Cloud Config Server。

2.1.5 HTTP 接口

Spring Cloud Config Server 的 EnvironmentController 类,提供了读取配置的 HTTP API 接口。简化代码如下:

// EnvironmentController.java

@RequestMapping(path = "/{name}/{profiles:.*[^-].*}", produces = MediaType.APPLICATION_JSON_VALUE)

@RequestMapping("/{name}-{profiles}.properties")

@RequestMapping("/{label}/{name}-{profiles}.properties")

@RequestMapping("{name}-{profiles}.json")

@RequestMapping("/{label}/{name}-{profiles}.json")

@RequestMapping({ "/{name}-{profiles}.yml", "/{name}-{profiles}.yaml" })

@RequestMapping({ "/{label}/{name}-{profiles}.yml", "/{label}/{name}-{profiles}.yaml" })

虽然 API 接口有点多,实际是三个参数的组合排列:

{name}:配置文件的名字。Spring Cloud Config Client 默认约定,使用应用名 spring.application.name 读取对应的配置文件。

例如说,对于应用名 spring.application.nameuser-application 时,会从 Spring Cloud Config Server 读取 user-application.yaml 等配置文件。

{profiles}:配置文件的 Profile,一般用于解决不同环境下的配置文件。Spring Cloud Config Client 默认约定,使用 spring.profiles.active 读取对应的 Profile 配置文件。

例如说,在 spring.profiles.activedev 时,会从 Spring Cloud Config Server 读取 user-application-dev.yaml 等配置文件。当然,不带有 Profile 的 user-application.yaml 配置文件也会被读取。

{label}:标签。在使用 Spring Cloud Config Server 使用 Git 作为存储器时,{label} 对应的是分支。

下面,我们来进行下 HTTP API 的简单使用:

① 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。启动日志如下:

// ... 省略部分非关键

// 使用 Git 存储器
2020-03-12 08:32:55.725 INFO 34494 --- [ main] c.i.s.l.c.ConfigServerApplication : The following profiles are active: git

// 服务的端口为 8888
2020-03-12 08:32:58.818 INFO 34494 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8888 (http) with context path ''

② 访问 http://127.0.0.1:8888/user-application.yaml 地址,获得名字为 user-application 的配置文件,结果如下:

order:
create-frequency-seconds: 120
pay-timeout-seconds: 60

符合预期。稍后我们创建的 labx-12-sc-config-user-application 项目,应用名为 user-application,所以读取的也是该配置文件。

另外,控制台可以看到 Spring Cloud Config Server 从 Git 拉取配置的日志如下:

2020-03-12 08:36:53.887  INFO 34494 --- [nio-8888-exec-1] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/var/folders/pz/c2rlnzcs2k1d45bh5675f10m0000gn/T/config-repo-333485710485523378/user-application.yml

2.2 使用 Spring Cloud Config Client

创建 labx-12-sc-config-user-application 项目,使用 Spring Cloud Config Client 从 Spring Cloud Config Server 读取配置,并使用 @ConfigurationProperties@Value 注解来进一步读取对应的配置。

2.2.1 引入依赖

pom.xml 文件中,引入 Spring Cloud Config Client 依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>labx-12</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>labx-12-sc-config-user-application</artifactId>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
</properties>

<!--
引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,进行依赖版本的管理,防止不兼容。
在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 开发团队推荐了三者的依赖关系
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 引入 SpringMVC 相关依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 引入 Spring Cloud Config Client 相关依赖,作为配置中心的客户端,并实现自动化配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>

</project>

通过 spring-cloud-starter-config 依赖,引入 Spring Cloud Config Client 相关依赖,作为配置中心的客户端,并实现自动化配置。

2.2.2 配置文件

创建 bootstrap.yml 配置文件,引入 Spring Cloud Config Client 相关配置项。配置内容如下:

spring:
application:
name: user-application

cloud:
# Spring Cloud Config Client 配置项,对应 ConfigClientProperties 类
config:
uri: http://127.0.0.1:8888 # Spring Cloud Config Server 的地址
name: ${spring.application.name} # 读取的配置文件的名字,默认为 ${spring.application.name}

spring.cloud.config 为 Spring Cloud Config Client 的配置项,对应 ConfigServerProperties 类。

  • uri:Spring Cloud Config Server 的地址。
  • name:读取的配置文件的名字,默认为 ${spring.application.name}。通过该默认值,实现了 Spring Cloud Config Client 的默认约定的读取。

友情提示:可能会有胖友好奇,为什么要将 Spring Cloud Config Client 的配置项添加到 bootstrap.yaml 配置文件,而不像我们之前的文章一样,添加到 application.yaml 配置文件中呢?

下面,我们来讲解下原因,可能有一点点难度,可以阅读一遍,选择性理解噢。

在 Spring Cloud 应用中,会先创建一个 Bootstrap Context(引导上下文),比 Spring Boot 创建 Application Context(应用上下文)更早初始化

Bootstrap Context 新增了一个 bootstrap.yaml 配置文件,保证和 Application Context 的 application.yaml 配置文件隔离

有了配置文件的隔离之后,Bootstrap Context 初始化的 Bean 从哪里来?Spring Cloud 新定义了专属于 Bootstrap Context 的自动化配置类的拓展点 BootstrapConfiguration,和 Spring Boot 为 Application Context 的自动化配置类的拓展点 EnableAutoConfiguration隔离,保证两个 Context 创建各自的 Bean。以 Spring Cloud Alibaba Nacos 的 spring.factories 举例子,如下图所示:`spring.factories` 示例

虽然说,Bootstrap Context 和 Application Context 做了这么多隔离,但是它们有一点是共享的,那就是 Environment。在 Spring 中,我们通过 Environment 获取属性配置,例如说 spring.application.name 对应的值是多少。

了解完这些之后,我们把它们串联在一起去思考一下,Bootstrap Context 的目的究竟是什么呢?通过 Bootstrap Context 的优先初始化,将配置加载到 Environment 中,提供给后面的 Application Context 使用。

举个贼重要的例子,稍后我们会在 bootstrap.yaml 添加 Spring Cloud Alibaba Nacos Config 相关的配置,这样 Bootstrap Context 在初始化时,通过 NacosConfigBootstrapConfiguration 创建 Nacos 相关的 Bean,然后实现从 Nacos 配置中心加载配置到 Environment 中。

如果我们把 Spring Cloud Alibaba Nacos Config 相关的配置添加在 application.yaml 中,那么可能无法保证 Nacos 相关的 Bean 被最先初始化,完成从 Nacos 获取配置,从而影响创建的 Bean。

友情提示:实际上将 Spring Cloud Alibaba Nacos Config 相关的配置添加在 application.yaml 中,也有办法解决,需要基于 Spring Boot 的 ApplicationContextInitializer 和 EnvironmentPostProcessor 拓展点,实现自定义的处理。

感兴趣的胖友,可以去看看 Apollo 或者 Nacos Config Spring Boot Starter 的源码。

😝 上面涉及的内容点有点多,不理解的胖友可以在看看如下两个资料:

2.2.3 OrderProperties

创建 OrderProperties 配置类,读取 order 配置项。代码如下:

@Component
@ConfigurationProperties(prefix = "order")
// @NacosConfigurationProperties(prefix = "order", dataId = "${nacos.config.data-id}", type = ConfigType.YAML)
public class OrderProperties {

/**
* 订单支付超时时长,单位:秒。
*/
private Integer payTimeoutSeconds;

/**
* 订单创建频率,单位:秒
*/
private Integer createFrequencySeconds;

// ... 省略 set/get 方法

}
  • 在类上,添加 @Component 注解,保证该配置类可以作为一个 Bean 被扫描到。
  • 在类上,添加 @ConfigurationProperties 注解,并设置 prefix = "order" 属性,这样它就可以读取前缀order 配置项,设置到配置类对应的属性上。

2.2.4 DemoController

创建 DemoController 类,提供测试 @ConfigurationProperties@Value 注入配置的两个 HTTP 接口。代码如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

@Autowired
private OrderProperties orderProperties;

/**
* 测试 @ConfigurationProperties 注解的配置属性类
*/
@GetMapping("/test01")
public OrderProperties test01() {
return orderProperties;
}

@Value(value = "${order.pay-timeout-seconds}")
private Integer payTimeoutSeconds;
@Value(value = "${order.create-frequency-seconds}")
private Integer createFrequencySeconds;

/**
* 测试 @Value 注解的属性
*/
@GetMapping("/test02")
public Map<String, Object> test02() {
Map<String, Object> result = new HashMap<>();
result.put("payTimeoutSeconds", payTimeoutSeconds);
result.put("createFrequencySeconds", createFrequencySeconds);
return result;
}

}

2.2.5 UserApplication

创建 UserApplication 类,作为应用启动类。代码如下:

@SpringBootApplication
public class UserApplication {

public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}

}

2.3 简单测试

① 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。

② 使用 UserApplication 类,启动用户服务。在 IDEA 控制台中,可以看到 Spring Cloud Config Client 相关的日志如下:

2020-03-12 21:14:19.742  INFO 53360 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://127.0.0.1:8888
2020-03-12 21:14:21.128 INFO 53360 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=user-application, profiles=[default], label=null, version=41b11704948ade722ada3deca531c001569e426e, state=null
2020-03-12 21:14:21.129 INFO 53360 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-configClient'}, BootstrapPropertySource {name='bootstrapProperties-https://github.com/YunaiV/demo-config-server.git/user-application.yml'}]

③ 使用浏览器,访问 http://127.0.0.1:8080/demo/test01 接口,测试 @ConfigurationProperties 注解的配置属性类,返回结果如下,符合预期:

{
"payTimeoutSeconds": 60,
"createFrequencySeconds": 120
}

④ 使用浏览器,访问 http://127.0.0.1:8080/demo/test02 接口,测试 @Value 注解的属性,返回结果如下,符合预期:

{
"payTimeoutSeconds": 60,
"createFrequencySeconds": 120
}

3. 多环境配置

示例代码对应仓库:

《芋道 Spring Boot 配置文件入门》「6. 多环境配置」中,我们介绍如何基于 spring.profiles.active 配置项,在 Spring Boot 实现多环境的配置功能。在本小节中,我们会在该基础之上,实现结合 Spring Cloud Config 的多环境配置。

通过 Spring Cloud Config Server 提供的 {profiles} 参数,我们可以读取不同环境对应的 {name}-{profiles}.yml 配置文件。

同时,在我们给项目设定 spring.profiles.active 配置项后,Spring Cloud Config Client 会按照约定,以该配置项作为 {profiles} 参数,自动从 Spring Cloud Config Server 读取对应的 {name}-{profiles}.yml 配置文件。

不过也要注意,Spring Cloud Config Client 还是会读取 {name}.yml 配置文件哈!

下面,我们来演示 Spring Cloud Config 的多环境配置的功能。无需重新搭建,直接复用「2. 快速入门」小节的两个项目即可。

3.1 修改 Git 仓库

修改 Git 仓库,增加用户服务 user-applicationdev 环境对应的 user-application-dev.yml 配置文件,和 prod 环境对应的 user-application-prod.yml 配置文件。内容如下图所示:

  • `user-application-dev.yml`
  • `user-application-prod.yml`

这两配置集都有 server.port 配置项,用于启动不同端口的服务器。😈 为什么选择 server.port 配置呢?因为 Spring Cloud 项目启动后,从日志中就可以看到生效的服务器端口,嘿嘿~

3.2 简单测试

① 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。

下面,我们使用命令行参数进行 --spring.profiles.active 配置项,实现不同环境,读取不同配置文件。

开发环境示例:直接在 IDEA 中,增加 --spring.profiles.active=dev 到 Program arguments 中。如下图所示:IDEA 配置 - dev

使用 UserApplication 应用,输出日志如下:

# 省略其它日志...
# Spring Cloud Config 日志
2020-03-12 21:17:40.661 INFO 57937 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-configClient'}, BootstrapPropertySource {name='bootstrapProperties-https://github.com/YunaiV/demo-config-server.git/user-application-dev.yml'}, BootstrapPropertySource {name='bootstrapProperties-https://github.com/YunaiV/demo-config-server.git/user-application.yml'}]

# Tomcat 启动日志
2020-03-12 21:17:41.251 INFO 57937 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)

通过看到 Spring Cloud Config 日志,不仅仅读取了 user-application-dev.yml 配置文件,也读取了 user-application.yml。并且,前者的优先级高于后者。

同时,Tomcat 启动在 8081 端口,也符合读取 dev 环境读取 user-application-dev.yml 配置文件。

生产环境示例:直接在 IDEA 中,增加 --spring.profiles.active=prod 到 Program arguments 中。如下图所示:IDEA 配置 - prod

使用 UserApplication 应用,输出日志如下:

# 省略其它日志...
# Spring Cloud Config 日志
2020-03-12 21:26:07.250 INFO 58057 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=user-application, profiles=[prod], label=null, version=78a4a881b2ff18884e577b662cedcd431abd2ec3, state=null

# Tomcat 启动日志
2020-03-12 21:26:07.822 INFO 58057 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8084 (http)

通过看到 Spring Cloud Config 日志,不仅仅读取了 user-application-prod.yml 配置文件,也读取了 user-application.yml。并且,前者的优先级高于后者。

同时,Tomcat 启动在 8084 端口,也符合读取 dev 环境读取 user-application-prod.yml 配置文件。

3.3 多哔哔几句

因为 Spring Cloud Config Server 在使用 Git 作为存储器时,支持使用 <label> 来选择不同的 Git 分支。所以,我们也可以选择给不同的环境,创建不同的 Git 分支

这样,我们就可以利用上 Git 分支提供的权限功能,让开发直接使用和修改 dev 对应的开发环境的分支,然后在没有问题之后,提供 Pull Request 到 prod 对应的生产环境的分支,进行审核和上线。如此,实现不同环境的配置的权限管理。

另外,推荐阅读艿艿好基友 didi 写的《Spring Cloud Config 采用 Git 存储时两种常用的配置策略》文章,非常有借鉴和参考意义。

4. 自动配置刷新(第一弹)

示例代码对应仓库:

在项目启动时,Spring Cloud Config Client 从 Spring Cloud Config Server 读取到配置时,考虑到后续的访问效率,是将配置缓存在本地内存中。也就是说,后续我们使用到的配置,都是直接访问内存中的配置,无需重复去 Spring Cloud Config Server 读取。

这样,就导致后续如果 Spring Cloud Config Server 配置发生变化时,项目是无法及时更新,即无法自动配置刷新

解决这个问题的方案有很多种,我们先来看第一种,直接使用 Spring Cloud Config Client 的 /actuator/refresh 接口,让 Spring Cloud Config Client 重新去 Spring Cloud Config Server 读取配置,从而刷新本地缓存。

下面,我们来演示 Spring Cloud Config 的自动配置刷新的第一弹。最终项目如下图:项目结构

4.1 搭建 Spring Cloud Config Server

直接使用「2. 快速入门」小节的 labx-12-sc-config-server-git 项目即可。

4.2 搭建 Spring Cloud Config Client

「2. 快速入门」小节的 labx-12-sc-config-user-application 项目,复制出 labx-12-sc-config-user-application-auto-refresh-by-actuator 项目来使用 Spring Cloud Config Client。

4.2.1 引入依赖

pom.xml 文件中,额外引入 Spring Boot Actuator 相关依赖。新增如下:

<!-- 实现对 Actuator 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

因为我们稍后调用的 /actuator/refresh 接口,是基于 Actuator 做的拓展 RefreshEndpoint 提供的。

4.2.2 配置文件

创建 application.yml 配置文件,额外增加 Spring Boot Actuator 配置项。配置如下:

management:
endpoints:
# Actuator HTTP 配置项,对应 WebEndpointProperties 配置类
web:
exposure:
include: refresh # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。

设置 management.endpoints.web.exposure.includerefresh,将 /actuator/refresh 接口开放出来。

友情提示:更多 Spring Boot Actuator 的内容,后续看下《芋道 Spring Boot 监控端点 Actuator 入门》文章。

4.2.3 @RefreshScope

在 Spring Cloud 中,提供了 @RefreshScope 注解,可以声明在 Bean 上,实现该 Bean 的配置刷新。

友情提示:对 @RefreshScope 注解的实现原理感兴趣的胖友,可以阅读《@RefreshScope 那些事》文章。

下面,我们将 @RefreshScope 注解添加在 DemoController 类上,实现 @Value 注解的属性的刷新。代码如下:

@RestController
@RequestMapping("/demo")
@RefreshScope // 加我加我加我!
public class DemoController {

// ... 省略其它代码

}

注意,不加 @RefreshScope 注解的情况下,使用 @ConfigurationProperties 注解的属性,依然可以配置刷新。

4.3 简单测试

① 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。使用 UserApplication 类,启动用户服务。

使用 curl 命令,请求 DemoController 提供的两个测试接口,获得当前的配置。过程如下:

# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":240}

# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"createFrequencySeconds":240,"payTimeoutSeconds":60}

② 将 Git 中的 user-application.yml 配置文件,将 order.create-frequency-seconds 配置项修改成 480,并 Push 到 Git 仓库。最终内容如下:

order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 480 # 订单创建频率,单位:秒

使用 curl 命令,请求 DemoController 提供的两个测试接口,获得当前的配置。过程如下:

# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":240}

# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"createFrequencySeconds":240,"payTimeoutSeconds":60}

配置没有,因为我们没有通知 Spring Cloud Config Client 配置变化,重新去 Spring Cloud Config Server 重新拉取。

③ 使用 Postman 模拟 POST 请求用户服务http://127.0.0.1:8080/actuator/refresh 地址,通知用户刷新配置,触发 Spring Cloud Config Client 从 Spring Cloud Config Server 重新拉取配置。返回结果如下:

[
"config.client.version",
"order.create-frequency-seconds"
]
  • 返回结果是一个数组,告诉我们变化的配置项有哪些。这里,可以看到我们刚修改的 "order.create-frequency-seconds" 配置项,符合预期。

同时,在 IDEA 控制台看到用户服务的日志,可以发现从 Spring Cloud Config Server 重新读取了配置内容:

2020-03-13 08:26:19.337  INFO 60921 --- [nio-8080-exec-8] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://127.0.0.1:8888
2020-03-13 08:26:21.506 INFO 60921 --- [nio-8080-exec-8] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=user-application, profiles=[default], label=null, version=c262a44be148d3dc2bfb4191f0bc18a22f7a67d5, state=null
2020-03-13 08:26:21.507 INFO 60921 --- [nio-8080-exec-8] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-configClient'}, BootstrapPropertySource {name='bootstrapProperties-https://github.com/YunaiV/demo-config-server.git/user-application.yml'}]

使用 curl 命令,请求 DemoController 提供的两个测试接口,获得当前的配置。过程如下:

# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":480}

# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"createFrequencySeconds":480,"payTimeoutSeconds":60}

配置成功刷新,美滋滋。

4.4 Webhook

主流的 Git 服务 Github、Gitlab 等等,都提供了 Webhook 机制,进行事件通知。也就是说,我们可以通过 Webhook,在我们每次 Push 最新配置到 Git 仓库时,自动调用 /actuator/refresh 接口,实现自动配置刷新,从而解放我们“勤劳的双手”!

下面,我们来配置一个 Webhook,步骤如下:

① 打开 Git 仓库的 Webhook 界面,如下图所示:Webhook 列表

② 点击「Add webhook」按钮,进入 Webhook 添加界面。设置如下图:Webhook 添加

友情提示:因为我们没有外网 IP,所以使用 NATAPP 帮我们转发出去。

如此,我们便配置完了一个 Webhook。下面,我们来进行下简单测试:

③ 将 Git 中的 user-application.yml 配置文件,将 order.create-frequency-seconds 配置项修改成 960,并 Push 到 Git 仓库。最终内容如下:

order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 960 # 订单创建频率,单位:秒

😈 此时,在 IDEA 控制台看到用户服务的日志,很不幸的会发现虽然 WebHook 回调报错了,日志内容如下:

2020-03-13 08:51:40.585  WARN 60921 --- [nio-8080-exec-7] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 144] (through reference chain: java.util.LinkedHashMap["repository"])]

这是一个悲伤的故事,因为解析 Webhook 的请求 body 报错了。具体胖友可以参考《解决使用 spring cloud config bus 使用 webhook 自动刷新出现的 400 问题》文章解决。

当然,也可以不解决,因为第一弹的自动配置刷新方案,我们基本不会去使用。因为,生产环境下基本是多节点的,而当前方案只刷新单节点,显然是太年轻~

5. 自动配置刷新(第二弹)

示例代码对应仓库:

在本小节,我们来学习 Spring Cloud Config 自动配置刷新的第二种方案,基于 Spring Cloud Bus 实现,可以通知到项目的所有节点,刷新本地缓存的配置。

Spring Cloud Bus 是事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与 Spring Cloud Config 联合实现热部署。

简单来说,Spring Cloud Bus 是基于 RabbitMQ、Kafka、RocketMQ 等消息队列,通过发送事件(消息),广播到订阅该事件(消息)的所有节点上。如此,我们就很好理解 Spring Cloud Config + Spring Cloud Bus 来实现自动配置刷新的整体架构,整体如下图所示:整体架构

  • 在我们修改完 Spring Cloud Config Server 配置中心的配置时后,调用其 /monitor 接口,发布 RefreshRemoteApplicationEvent 事件到 Spring Cloud Bus 中。
  • 使用了 Spring Cloud Config Client 的项目,在启动时会订阅 RefreshRemoteApplicationEvent 事件。这样,在从 Spring Cloud Bus 接收到该事件时,会使用 Spring Cloud Config Client 重新从 Server 拉取配置,从而自动刷新本地缓存的配置。

友情提示:其实本质上,是基于消息队列实现广播消费,刷新本地缓存,嘿嘿~

下面,我们来演示 Spring Cloud Config 的自动配置刷新的第二弹。最终项目如下图:项目结构

5.1 搭建 Spring Cloud Config Server

「2. 快速入门」小节的 labx-12-sc-config-user-application 项目,复制出 labx-12-sc-config-server-git-auto-refresh-by-bus 项目来接入 Spring Cloud Bus。

5.1.1 引入依赖

修改 pom.xml 文件中,额外引入 Spring Cloud Bus 相关依赖。新增如下:

<!-- 引入基于 RabbitMQ 的 Spring Cloud Bus 的实现的依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

<!-- 引入 Spring Cloud Config Monitor 依赖,提供 `/monitor` 接口,用于刷新配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-monitor</artifactId>
</dependency>

① 引入的两个依赖的作用,胖友可以看看艿艿添加的注释,明明白白~

② Spring Cloud Bus 除了有基于 RabbitMQ 的实现,还有 Kafka、RocketMQ 消息队列的,胖友后续可以自己尝试其它的:

5.1.2 配置文件

修改 application.yml 配置文件,增加 spring.rabbitmq 配置项,从而初始化 RabbitMQ 客户端,提供给 Spring Cloud Bus RabbitMQ 使用。完整配置如下:

server:
port: 8888

spring:
application:
name: demo-config-server

profiles:
active: git # 使用的 Spring Cloud Config Server 的存储器方案
# Spring Cloud Config 相关配置项
cloud:
config:
server:
# Spring Cloud Config Server 的 Git 存储器的配置项,对应 MultipleJGitEnvironmentProperties 类
git:
uri: https://github.com/YunaiV/demo-config-server.git # Git 仓库地址
search-paths: / # 读取文件的根地址
default-label: master # 使用的默认分支,默认为 master
# username: ${CODING_USERNAME} # 账号
# password: ${CODING_PASSWORD} # 密码

# RabbitMQ 相关配置项
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

如果胖友本地暂未安装 RabbitMQ 的话,可以参考艿艿写的《芋道 RabbitMQ 安装部署》文章。

5.2 搭建 Spring Cloud Config Client

「2. 快速入门」小节的 labx-12-sc-config-user-application 项目,复制出 labx-12-sc-config-user-application-auto-refresh-by-bus 项目来接入 Spring Cloud Bus。

5.2.1 引入依赖

修改 pom.xml 文件中,额外引入 Spring Cloud Bus 相关依赖。新增如下:

<!-- 引入基于 RabbitMQ 的 Spring Cloud Bus 的实现的依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

5.2.2 配置文件

创建 application.yml 配置文件,增加 spring.rabbitmq 配置项,从而初始化 RabbitMQ 客户端,提供给 Spring Cloud Bus RabbitMQ 使用。配置如下:

spring:
# RabbitMQ 相关配置项
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

5.2.3 @RefreshScope

@RefreshScope 注解添加在 DemoController 类上,实现 @Value 注解的属性的刷新。代码如下:

@RestController
@RequestMapping("/demo")
@RefreshScope // 加我加我加我!
public class DemoController {

// ... 省略其它代码

}

5.3 简单测试

① 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。使用 UserApplication 类,启动用户服务。

使用 curl 命令,请求 DemoController 提供的两个测试接口,获得当前的配置。过程如下:

# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":240}

# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"createFrequencySeconds":240,"payTimeoutSeconds":60}

② 将 Git 中的 user-application.yml 配置文件,将 order.create-frequency-seconds 配置项修改成 480,并 Push 到 Git 仓库。最终内容如下:

order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 480 # 订单创建频率,单位:秒

使用 curl 命令,请求 DemoController 提供的两个测试接口,获得当前的配置。过程如下:

# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":240}

# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"createFrequencySeconds":240,"payTimeoutSeconds":60}

配置没有,因为我们没有通知 Spring Cloud Config Client 配置变化,重新去 Spring Cloud Config Server 重新拉取。

③ 使用 Postman 模拟 POST 请求 Spring Cloud Config Serverhttp://127.0.0.1:8888/monitor 地址,实现 Spring Cloud Config Server 使用 Spring Cloud Bus 发送事件(消息)到 RabbitMQ 中。如下图所示:Postman 模拟请求

  • path 参数,设置要自动配置刷新的服务名。这里设置为 * 表示通知所有服务。

此时在 IDEA 的控制台中,我们会看到 Spring Cloud Config Server 会打印 Spring Cloud Config Monitor 的日志如下:

2020-03-14 08:10:42.459  INFO 33375 --- [io-8888-exec-10] o.s.c.c.monitor.PropertyPathEndpoint     : Refresh for: *

用户服务通过 Spring Cloud Bus 会接收到来自 RabbitMQ 的事件(消息),会触发 Spring Cloud Config Client 从 Spring Cloud Config Server 重新拉取配置。

此时在 IDEA 的控制台中,我们会看到用户服务的日志,可以发现从 Spring Cloud Config Server 重新读取了配置内容:

# Spring Cloud Config 相关日志
2020-03-14 08:13:17.893 INFO 33721 --- [rKC4AuvtW_ZYw-1] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://127.0.0.1:8888
2020-03-14 08:13:18.945 INFO 33721 --- [rKC4AuvtW_ZYw-1] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=user-application, profiles=[default], label=null, version=65469edc06892d76d2b3d3e35b4cdd5ff71d3c52, state=null

# Spring Cloud Bus 相关日志
2020-03-14 08:13:18.983 INFO 33721 --- [rKC4AuvtW_ZYw-1] o.s.cloud.bus.event.RefreshListener : Received remote refresh request. Keys refreshed []

使用 curl 命令,请求 DemoController 提供的两个测试接口,获得当前的配置。过程如下:

# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":480}

# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"createFrequencySeconds":480,"payTimeoutSeconds":60}

配置成功刷新,美滋滋。

5.4 Webhook

「4. 自动刷新配置(第一弹)」小节一样,我们也可以使用 Webhook 来自动回调 Spring Cloud Config Server/monitor 接口,实现自动配置刷新,真正解放我们“勤劳的双手”!

下面,我们来重新演示一下,步骤如下:

① 在 Git 仓库中,创建一个回调 Spring Cloud Config Server/monitor 接口的 WebHook,如下图所示:WebHook

② 将 Git 中的 user-application.yml 配置文件,将 order.create-frequency-seconds 配置项修改成 960,并 Push 到 Git 仓库。最终内容如下:

order:
pay-timeout-seconds: 60 # 订单支付超时时长,单位:秒。
create-frequency-seconds: 960 # 订单创建频率,单位:秒

在完成 Push 动作之后,我们就会收到 Git Webhook 的回调,从而实现完整的自动配置刷新功能。具体的日志,胖友自己在 IDEA 的控制台查看。

使用 curl 命令,请求 DemoController 提供的两个测试接口,获得当前的配置。过程如下:

# 测试 `@ConfigurationProperties` 注解的配置属性类
$ curl http://127.0.0.1:8080/demo/test01
{"payTimeoutSeconds":60,"createFrequencySeconds":960}

# 测试 `@Value` 注解的属性
$ curl http://127.0.0.1:8080/demo/test02
{"createFrequencySeconds":960,"payTimeoutSeconds":60}

配置成功刷新,美滋滋。

6. 自动配置刷新(第三弹)

示例代码对应仓库:

通过 @ConfigurationProperties 或者 @Value + @RefreshScope 注解,已经能够满足我们绝大多数场景下的自动刷新配置的功能。但是,在一些场景下,我们仍然需要实现对配置的监听,执行自定义的逻辑

例如说,当数据库连接的配置发生变更时,我们需要通过监听该配置的变更,重新初始化应用中的数据库连接,从而访问到新的数据库地址。

又例如说,当日志级别发生变更时,我们需要通过监听该配置的变更,设置应用中的 Logger 的日志级别,从而后续的日志打印可以根据新的日志级别。

在 Spring Cloud 中,在 Environment 的属性配置发生变化时,会发布 EnvironmentChangeEvent 事件。这样,我们只需要实现 EnvironmentChangeEvent 事件的监听器,就可以进行自定义的逻辑处理。

例如说,Spring Cloud 内置了 LoggingRebinder 监听器,实现了对 EnvironmentChangeEvent 事件的监听,在发现 logging.level 配置项发生变化时,重新设置日志组件的日志级别。如下图所示:LoggingRebinder 源码

补充:最近看了下文档,可以通过 @RefreshScope 注解,实现数据源的动态变化,具体可以看看《SpringCloud 运行时刷新数据源相关配置》文章。也就是说,不一定需要通过实现对 EnvironmentChangeEvent 事件的监听处理。

下面,我们来演示 Spring Cloud 的 EnvironmentChangeEvent 的事件监听器,通过内置的 LoggingRebinder 监听器,以及稍后我们会自定义一个简单的监听器。无需重新搭建,直接复用「自动配置刷新(第一弹)」小节的两个项目即可。最终项目如下图所示:项目结构

6.1 LoggingRebinder

通过 LoggingRebinder 监听器,我们可以实现日志级别的动态修改。

先在 DemoController 类中,增加一个打印日志的 API 接口。代码如下:

// DemoController.java

private Logger logger = LoggerFactory.getLogger(getClass());

@GetMapping("/logger")
public void logger() {
logger.debug("[logger][测试一下]");
}
  • 如果 DemoController 对应的 Logger 日志级别是 DEBUG 以上,则无法打印出日志。

然后,开始我们来测试日志级别的动态修改的功能:

① 在 Git 中的 user-application.yml 配置文件,增加 logging.level.cn.iocoder.springcloud.labx12.userapplication.controller.DemoController 配置项为 INFO,即让 DemoController 使用 INFO 日志级别。修改完成,记得推送到 Git 仓库噢。

然后,使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。使用 UserApplication 类,启动用户服务。

最后,请求 http://127.0.0.1:8080/demo/logger 接口,控制台并未打印日志,因为当前日志级别是 INFO

② 在 Git 中的 user-application.yml 配置文件,增加 logging.level.cn.iocoder.springcloud.labx12.userapplication.controller.DemoController 配置项为 DEBUG,即让 DemoController 使用 DEBUG 日志级别。修改完成,记得推送到 Git 仓库噢。

然后,使用 Postman 模拟 POST 请求用户服务http://127.0.0.1:8080/actuator/refresh 地址,通知用户刷新配置,触发 Spring Cloud Config Client 从 Spring Cloud Config Server 重新拉取配置。

最终,控制台打印日志,因为当前日志级别是 DEBUG。日志内容如下:

2020-03-14 09:21:24.022 DEBUG 50417 --- [nio-8080-exec-3] c.i.s.l.u.controller.DemoController      : [logger][测试一下]
  • 符合预期。

6.2 DemoEnvironmentChangeListener

我们来实现一个自定义的 DemoEnvironmentChangeListener 监听器,打印下变化配置项的最新值。代码如下:

@Component
public class DemoEnvironmentChangeListener implements ApplicationListener<EnvironmentChangeEvent> {

private Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private ConfigurableEnvironment environment;

@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
for (String key : event.getKeys()) {
logger.info("[onApplicationEvent][key({}) 最新 value 为 {}]", key, environment.getProperty(key));
}
}

}

然后,开始我们来测试 DemoEnvironmentChangeListener 的功能:

① 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。使用 UserApplication 类,启动用户服务。

② 在 Git 中的 user-application.yml 配置文件,增加 test 配置项为 true,只是为了修改下配置。修改完成,记得推送到 Git 仓库噢。

然后,使用 Postman 模拟 POST 请求用户服务http://127.0.0.1:8080/actuator/refresh 地址,通知用户刷新配置,触发 Spring Cloud Config Client 从 Spring Cloud Config Server 重新拉取配置。

最终,我们在 IDEA 控制台可以看到 DemoEnvironmentChangeListener 打印如下日志,符合预期:

2020-03-14 09:29:09.439  INFO 50417 --- [nio-8080-exec-6] .i.s.l.u.l.DemoEnvironmentChangeListener : [onApplicationEvent][key(config.client.version) 最新 value 为 b484b8cae6d99a392a0e7ce59fb23f44d3030b3e]
2020-03-14 09:29:09.439 INFO 50417 --- [nio-8080-exec-6] .i.s.l.u.l.DemoEnvironmentChangeListener : [onApplicationEvent][key(test) 最新 value 为 true]

7. 配置加密

示例代码对应仓库:

考虑到安全性,我们可能最好将配置文件中的敏感信息进行加密。例如说,MySQL 的用户名密码、第三方平台的 Token 令牌等等。

Spring Cloud Config 是唯一内置配置加密的功能的配置中心,这点是比 Nacos 和 Apollo 强大的地方,它们都需要结合 Jasypt 来实现该功能,并且存在不兼容的问题。

Spring Cloud Config 的配置的加解密行为,是在 Server 端完成,对 Client 端是完全透明的。例如说,我们将 xx-password 配置项加密存储到 Git 仓库后,Spring Cloud Config Client 在从 Spring Cloud Config Server 请求该配置项时,Server 会从 Git 仓库获取 xx-password 配置项,并解密返回给 Spring Cloud Config Client。

下面,我们来演示 Spring Cloud Config 的配置加密的功能。无需重新搭建,直接改造「2. 快速入门」小节的两个项目即可。最终项目如下图:项目结构

7.1 搭建 Spring Cloud Config Server

直接使用「2. 快速入门」小节的 labx-12-sc-config-server-git 项目即可,开启配置加密功能。

7.1.1 安装 JCE

因为 Spring Cloud Config Server 的配置加密功能,是基于 JCE(Java Cryptography Extension)实现,在使用它实现 ACE 加密时,会抛出如下异常:

java.security.InvalidKeyException: Illegal key size

原因是,默认安装的 JDK 或者 JRE 包含 JCE 的 policy 文件是受限,和美国对软件出口的控制有关系。

解决办法也很简单,去 Oracle 官方下载 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files,然后覆盖掉 JDK 或 JRE 中的 local_policy.jarUS_export_policy.jar 即可。下载地址为:

具体操作如下图所示:操作图

7.1.2 配置文件

创建 bootstrap.yml 配置文件,增加加密功能相关的配置项。配置如下:

友情提示:一定要放在 bootstrap.yml 配置文件中,不然配置解密功能是无法生效的!

# 加密配置项,对应 KeyProperties 类
encrypt:
# 对称加密
key: yudaoyuanma # 对称加密 key
salt: dfaad7761f0729b35ec3b3543604eda7 # 对称加密 salt,默认为 "deadbeef",必须是 16 进制

encrypt 为加密配置项,对应 KeyProperties 类。Spring Cloud Config 支持对称加密非对称加密,后者安全性更高。不过考虑到快速学习,这里先采用对称加密:

  • key:对称加密的 key。
  • salt:对称加密的 salt,默认为 "deadbeef"(死牛肉!!!)。注意,生产环境下推荐自定义,必须是 16 进制,可以通过 Hex(16 进制)随机生成计算器 来生成。

7.1.3 EncryptionController

在 Spring Cloud Config Server 中,内置 EncryptionController 提供加解密功能的 HTTP 接口,比较常用的有两个:

  • 加密:curl {CONFIG_SERVER}/encrypt -d 需要加密的字符串
  • 解密:curl {CONFIG_SERVER}/decrypt -d 需要解密的字符串

这样,我们可以使用 /encrypt 接口来加密需要的配置项的,然后添加到 Git 仓库中,嘿嘿。

7.2 搭建 Spring Cloud Config Client

直接使用「2. 快速入门」小节的 labx-12-sc-config-user-application 项目即可,无需改造。

考虑到能够方便读取稍后测试的 xx-password 配置项,我们在 DemoController 中添加读取该配置项的接口。代码如下:

// DemoController.java

@Value(value = "${xx-password:''}")
private String xxPassword;

@GetMapping("/xx_password")
public String xxPassword() {
return xxPassword;
}

7.3 简单测试

现在来简单测试下,我们会将 xx-password 配置项的值 "wosshimima" 使用 Spring Cloud Config Server 进行加密,存储到 Git 仓库中,然后使用 Spring Cloud Config Client 从 Spring Cloud Config Client,看看是否能够读取到正确结果。

① 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。

② 调用 Spring Cloud Config Server 的 /encrypt 接口,将 "wosshimima" 进行解密。操作如下:

$ curl http://127.0.0.1:8888/encrypt -d woshimima
5fca0df4439ee4b2830195555934985442efee67fe1e4bb4cce6fde2ff081a10

然后,添加 xx-password 配置项为 5fca0df4439ee4b2830195555934985442efee67fe1e4bb4cce6fde2ff081a10 到 Git 仓库中。如下图所示:Git 配置项

注意,需要使用 '{cipher}' 进行包裹,这样 Spring Cloud Config Server 才能知道该配置项的值是经过解密的,需要进行解密

③ 使用 UserApplication 类,启动用户服务。

然后,请求用户服务的 http://127.0.0.1:8080/demo/xx_password 接口,返回结果为 woshimima,符合预期~

7.4 非对称加密

「7.1.2 配置文件」中,我们演示了对称加密的配置方式,考虑到线上更多使用的是非对称加密,所以艿艿来补充一波哈~

友情提示:如下所有的操作,都是在 Spring Cloud Config Server 下进行,即 labx-12-sc-config-server-git 项目。

① 使用 keytool 命令来生成 Key Store。操作如下:

keytool -genkeypair -alias mytestkey -keyalg RSA -dname "CN=Config Server,OU=Unit,O=Organization,L=City,S=State,C=CN" -keypass nicainicai -keystore configserver.jks -storepass buzhidao

具体的参数含义如下:

  • -genkeypair:创建密钥对
  • -alias:产生别名,默认为 "mykey"
  • -keyalg:指定密钥的算法,例如说 RSA、DSA。默认为 DSA
  • -dname:指定证书拥有者信息,例如:"CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码"
  • -keypass:指定别名条目的密码(私钥的密码)
  • -keystore:指定密钥库的名称(产生的各类信息将不在 .keystore 文件中)
  • -storepass:指定密钥库的密码(获取 keystore 信息所需的密码)

执行完成后,在当前目录我们会获得 configserver.jks。我们需要将它复制到 Spring Cloud Config Server 项目的 resources 目录下,如下图所示:configserver.jks`

② 修改 bootstrap.yml 配置文件,设置使用 KeyStore。完整配置如下:

# 加密配置项,对应 KeyProperties 类
encrypt:
# 对称加密
# key: yudaoyuanma # 对称加密 key
# salt: dfaad7761f0729b35ec3b3543604eda7 # 对称加密 salt,默认为 "deadbeef",必须是 16 进制

# 非对应加密
key-store:
location: classpath:/configserver.jks # jks 文件所在的路径
password: buzhidao # 对应 storepass 参数
alias: mytestkey # 对应 alias 参数
secret: nicainicai # 对应 keypass 参数

③ 因为 Maven 配置下,不会将 *.jks 打包到项目中,所以我们需要修改下 pom.xml 文件的 <build /> 属性如下:

<build>
<resources>
<resource>
<directory>src/main/resource</directory>
<includes>
<include>**/*.yml</include> <!-- 将 .yml 后缀包含进去 -->
<include>**/*.jks</include> <!-- 将 .jks 后缀包含进去 -->
</includes>
<filtering>false</filtering> <!-- 不进行过滤 -->
</resource>
</resources>
</build>

④ 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。

然后,调用 Spring Cloud Config Server 的 /encrypt 接口,将 "wosshimima" 进行解密。操作如下:

$ curl http://127.0.0.1:8888/encrypt -d woshimima
AQCl9dA/CSkWrtWlRxkjUP7GwTwkgs0lN3zC8gbpeufLZQ89IeaIqj90Bo5g36IGtR2O7w9idzOA1TACGW+RNhpv4GHWgO6+7vVPX/wo+4Wm1me9McnVLREhg9J+c+SIPNPYEPV+wliPon/K5Dl5bUP9CiWPE7IFNDegAN9w5DELb8i6OM2z736X8zqBePpv1s2zt0D5RhxEFgV1gnJ9cJHK6XImC9im5FqF5zKwmjJrHe7rBdFrzqPT1b1gzyrxk0MxtIYPXzfZT89xvBqul9tUP/V/rI4RXZEfcClz5t+ahgFTh6wERjTztlEnA2e86EODOkR42/2hi/HUyCcqG4WSU+K1aJ0q15JLEORvLFbZKvZZ/ifyk/OKaW8vUdJ1uko=

成功!后续的使用,和对称加密是一致的,胖友可以按照「7.3 简单测试」自己玩一会~

8. 与注册中心集成

示例代码对应仓库:

Spring Cloud Config 对 Spring Cloud 注册中心进行了集成,这样我们可以将 Spring Cloud Config Server 注册到注册中心,如此Spring Cloud Config Client 可以从注册中心获取到 Server 的地址,从而实现 Server 的高可用。

下面,我们来演示 Spring Cloud Config 与注册中心集成的示例。最终项目如下图:项目结构

8.1 搭建 Spring Cloud Config Server

「2. 快速入门」小节的 labx-12-sc-config-user-application 项目,复制出 labx-12-sc-config-server-git-nacos 项目来接入 Spring Cloud 注册中心,这里我们选择使用 Nacos 作为注册中心。

友情提示:对 Nacos 不了解的胖友,可以后续可以看看《芋道 Spring Cloud Alibaba 配置中心 Nacos 入门》文章。

8.1.1 引入依赖

修改 pom.xml 文件,引入 spring-cloud-starter-alibaba-nacos-discovery 依赖。完整内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>labx-12</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>labx-12-sc-config-server-git-nacos</artifactId>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
<spring.cloud.alibaba.version>2.2.0.RELEASE</spring.cloud.alibaba.version>
</properties>

<!--
引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,进行依赖版本的管理,防止不兼容。
在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 开发团队推荐了三者的依赖关系
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 引入 Spring Cloud Config Server 相关依赖,实现配置中心的服务端,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

<!-- 引入 Spring Cloud Alibaba Nacos Discovery 相关依赖,将 Nacos 作为注册中心,并实现对其的自动配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>

</project>

8.1.2 配置文件

修改 application.yml 配置文件,增加 spring.cloud.nacos.discovery 配置项,实现 Spring Cloud Alibaba Nacos Discovery 的配置。完整配置如下:

server:
port: 8888

spring:
application:
name: demo-config-server

profiles:
active: git # 使用的 Spring Cloud Config Server 的存储器方案

# Spring Cloud Config 相关配置项
cloud:
config:
server:
# Spring Cloud Config Server 的 Git 存储器的配置项,对应 MultipleJGitEnvironmentProperties 类
git:
uri: https://github.com/YunaiV/demo-config-server.git # Git 仓库地址
search-paths: / # 读取文件的根地址
default-label: master # 使用的默认分支,默认为 master
# username: ${CODING_USERNAME} # 账号
# password: ${CODING_PASSWORD} # 密码

# Spring Cloud Nacos Discovery 相关配置项
nacos:
# Nacos 作为注册中心的配置项,对应 NacosDiscoveryProperties 配置类
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
service: ${spring.application.name} # 注册到 Nacos 的服务名。默认值为 ${spring.application.name}。

8.2 搭建 Spring Cloud Config Client

「2. 快速入门」小节的 labx-12-sc-config-user-application 项目,复制出 labx-12-sc-config-user-application-nacos 项目来接入 Spring Cloud 注册中心。

8.2.1 引入依赖

修改 pom.xml 文件,引入 spring-cloud-starter-alibaba-nacos-discovery 依赖。完整内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>labx-12</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>labx-12-sc-config-user-application-nacos</artifactId>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
<spring.cloud.alibaba.version>2.2.0.RELEASE</spring.cloud.alibaba.version>
</properties>

<!--
引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,进行依赖版本的管理,防止不兼容。
在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 开发团队推荐了三者的依赖关系
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 引入 SpringMVC 相关依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 引入 Spring Cloud Config Client 相关依赖,作为配置中心的客户端,并实现自动化配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

<!-- 引入 Spring Cloud Alibaba Nacos Discovery 相关依赖,将 Nacos 作为注册中心,并实现对其的自动配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>

</project>

8.2.2 配置文件

修改 bootstrap.yml 配置文件,增加 spring.cloud.nacos.discovery 配置项,实现 Spring Cloud Alibaba Nacos Discovery 的配置。完整配置如下:

spring:
application:
name: user-application

cloud:
# Spring Cloud Config Client 配置项,对应 ConfigClientProperties 类
config:
name: ${spring.application.name} # 读取的配置文件的名字,默认为 ${spring.application.name}
discovery:
enabled: true # 是否使用注册发现,获取配置中心的地址,默认为 false
service-id: demo-config-server # 配置中心的服务名

# Spring Cloud Nacos Discovery 相关配置项
nacos:
# Nacos 作为注册中心的配置项,对应 NacosDiscoveryProperties 配置类
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
service: ${spring.application.name} # 注册到 Nacos 的服务名。默认值为 ${spring.application.name}。

重点看下在 spring.cloud.config 配置项,我们去掉了 uri 配置项,并增加 discovery 配置项来配置和 Spring Cloud 注册中心的集成。这里我们设置 service-iddemo-config-server,就是我们搭建的 Spring Cloud Config Server 的服务名。

8.3 简单测试

① 使用 ConfigServerApplication 类,启动 Spring Cloud Config Server。

启动完成后,在 Nacos 控制台可以看到对应的 demo-config-server 服务,如下图所示:Nacos 控制台

② 使用 UserApplication 类,启动用户服务。在 IDEA 控制台中,可以看到 Spring Cloud Config Client 相关的日志如下:

2020-03-15 16:54:32.053  INFO 5581 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://10.171.1.115:8888/

成功从注册中心获取到 demo-config-server 配置中心的地址,从而实现从配置中心加载配置。

666. 彩蛋

至此,我们已经完成 Spring Cloud Config 的学习。如下是 Nacos 相关的官方文档:

嘿嘿,实际项目中,还是更为推荐使用 Apollo 或者 Nacos 作为配置中心,感兴趣的胖友可以阅读如下文章:

文章目录
  1. 1. 1. 概述
  2. 2. 2. 快速入门
    1. 2.1. 2.1 搭建 Spring Cloud Config Server
      1. 2.1.1. 2.1.1 创建 Git 仓库
      2. 2.1.2. 2.1.2 引入依赖
      3. 2.1.3. 2.1.3 配置文件
      4. 2.1.4. 2.1.4 ConfigServerApplication
      5. 2.1.5. 2.1.5 HTTP 接口
    2. 2.2. 2.2 使用 Spring Cloud Config Client
      1. 2.2.1. 2.2.1 引入依赖
      2. 2.2.2. 2.2.2 配置文件
      3. 2.2.3. 2.2.3 OrderProperties
      4. 2.2.4. 2.2.4 DemoController
      5. 2.2.5. 2.2.5 UserApplication
    3. 2.3. 2.3 简单测试
  3. 3. 3. 多环境配置
    1. 3.1. 3.1 修改 Git 仓库
    2. 3.2. 3.2 简单测试
    3. 3.3. 3.3 多哔哔几句
  4. 4. 4. 自动配置刷新(第一弹)
    1. 4.1. 4.1 搭建 Spring Cloud Config Server
    2. 4.2. 4.2 搭建 Spring Cloud Config Client
      1. 4.2.1. 4.2.1 引入依赖
      2. 4.2.2. 4.2.2 配置文件
      3. 4.2.3. 4.2.3 @RefreshScope
    3. 4.3. 4.3 简单测试
    4. 4.4. 4.4 Webhook
  5. 5. 5. 自动配置刷新(第二弹)
    1. 5.1. 5.1 搭建 Spring Cloud Config Server
      1. 5.1.1. 5.1.1 引入依赖
      2. 5.1.2. 5.1.2 配置文件
    2. 5.2. 5.2 搭建 Spring Cloud Config Client
      1. 5.2.1. 5.2.1 引入依赖
      2. 5.2.2. 5.2.2 配置文件
      3. 5.2.3. 5.2.3 @RefreshScope
    3. 5.3. 5.3 简单测试
    4. 5.4. 5.4 Webhook
  6. 6. 6. 自动配置刷新(第三弹)
    1. 6.1. 6.1 LoggingRebinder
    2. 6.2. 6.2 DemoEnvironmentChangeListener
  7. 7. 7. 配置加密
    1. 7.1. 7.1 搭建 Spring Cloud Config Server
      1. 7.1.1. 7.1.1 安装 JCE
      2. 7.1.2. 7.1.2 配置文件
      3. 7.1.3. 7.1.3 EncryptionController
    2. 7.2. 7.2 搭建 Spring Cloud Config Client
    3. 7.3. 7.3 简单测试
    4. 7.4. 7.4 非对称加密
  8. 8. 8. 与注册中心集成
    1. 8.1. 8.1 搭建 Spring Cloud Config Server
      1. 8.1.1. 8.1.1 引入依赖
      2. 8.1.2. 8.1.2 配置文件
    2. 8.2. 8.2 搭建 Spring Cloud Config Client
      1. 8.2.1. 8.2.1 引入依赖
      2. 8.2.2. 8.2.2 配置文件
    3. 8.3. 8.3 简单测试
  9. 9. 666. 彩蛋