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

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


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

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

本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labslabx-26 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

《芋道 ZooKeeper 极简入门》文章中,我们一起完成了 ZooKeeper 的学习,并完成了 ZooKeeper 服务器的搭建。

本文我们来学习 Spring Cloud ZooKeeper 提供的 spring-cloud-zookeeper-config 组件,接入 Zookeeper 作为配置中心,实现服务的统一配置管理。

2. 快速入门

示例代码对应仓库:labx-26-sc-zookeeper-config-demo

本小节,我们会在 ZooKeeper 服务中定义配置,并使用 @ConfigurationProperties@Value 注解,读取该配置。

友情提示:如果胖友看过《芋道 Spring Boot 配置文件入门》「2. 自定义配置」小节,就会发现本小节是对标这块的内容。

如果没看过,也没关系,艿艿只是提一下而已,嘿嘿。继续往下看即可。

下面,我们来创建一个 labx-26-sc-zookeeper-config-demo 示例项目,进行快速入门。最终项目代码如下图所示:

`labx-26-sc-zookeeper-config-demo` 项目

2.1 引入依赖

pom.xml 文件中,主要引入 Spring Cloud ZooKeeper Config 相关依赖。代码如下:

<?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-26</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>labx-26-sc-zookeeper-config-demo</artifactId>

<properties>
<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 ZooKeeper Config 相关依赖,将 ZooKeeper 作为配置中心,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>
</dependencies>

</project>

引入 spring-cloud-starter-zookeeper-config 依赖,将 ZooKeeper 作为配置中心,并实现对它的自动配置。

2.2 配置文件

创建 bootstrap.yaml 配置文件,添加 ZooKeeper Config 配置项。配置如下:

spring:
application:
name: demo-application

cloud:
zookeeper:
connect-string: 127.0.0.1:2181
# ZooKeeper 作为配置中心的配置项,对应 ZooKeeperConfigProperties 配置类
config:
root: /config # Zookeeper 数据存储的根节点,默认为 /config
default-context: application # 默认读取 Zookeeper 配置的目录,默认为 application
profile-separator: ',' # 多环境目录分隔符,默认为 ,
watcher:
enabled: true # 是否开启 Watch 功能,监听 Zookeeper 数据的变化。默认为 true,实现实时监听配置的更新

spring.cloud.zookeeper 配置项,设置 ZooKeeper 客户端的配置,对应 ZooKeeperProperties 配置类。

connect-string 配置项,设置 ZooKeeper 服务器的地址。

spring.cloud.zookeeper.config 配置项,设置 ZooKeeper Config 配置项,对应 ZooKeeperConfigProperties 配置类。

root 配置项,设置 ZooKeeper 数据存储的根节点,默认为 /config。稍后,我们来具体演示下哈~

default-context 配置项,读取 Zookeeper 配置的默认目录,默认为 application。对于 Zookeeper Config 来说,它会读取 Zookeeper /{root}/{spring.application.name}//{root}/{default-context}/ 目录下的节点,作为配置项。如下图所示:

Zookeeper Config 配置项的示例

profile-separator 配置项,多环境目录分隔符,默认为 ,。举个例子,在我们设置 Spring Profile 为 dev 时,会多读取 Zookeeper /{root}/{spring.application.name},dev//{root}/{default-context},dev/ 目录下的节点,作为配置项。

watcher.enabled 配置,设置是否开启 Watch 功能,监听 Zookeeper 数据的变化。默认为 true,实现实时监听配置的更新。

2.3 创建 Zookeeper 配置项

使用 ZooKeeper Inspector 可视化工具,创建 demo-application 应用的两个配置项:

  • order.pay-timeout-seconds=60
  • order.create-frequency-seconds=120

Zookeeper 配置项

友情提示:图艿艿截错了,按照文字版为准,嘿嘿~

2.4 OrderProperties

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

@Component
@ConfigurationProperties(prefix = "order")
public class OrderProperties {

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

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

// ... 省略 setter/getter 方法

}

① 在类上,添加 @Component 注解,保证该配置类可以作为一个 Bean 被扫描到。

② 在类上,添加 @ConfigurationProperties 注解,并设置 prefix = "order" 属性,这样它就可以读取前缀order 配置项,设置到配置类对应的属性上。

2.5 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}") // @NacosValue(value = "${order.pay-timeout-seconds}")
private Integer payTimeoutSeconds;
@Value(value = "${order.create-frequency-seconds}") // @NacosValue(value = "${order.create-frequency-seconds}")
private Integer createFrequencySeconds;

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

}

2.6 DemoApplication

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

@SpringBootApplication
public class DemoApplication {

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

}

2.7 简单测试

① 使用 DemoApplication 启动示例应用。在 IDEA 控制台中,可以看到 Zookeeper 相关的日志如下:

// 加载了 ZooKeeper `/config/demo-application` 和 `/config/application` 目录下的节点,作为配置项
2020-06-09 07:55:51.761 INFO 13928 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-/config/demo-application'}, BootstrapPropertySource {name='bootstrapProperties-/config/application'}]

友情提示:ZooKeeper Config 会额外加载 ZooKeeper /config/application 目录下的节点,实现多应用之间的配置共享

② 使用浏览器,访问 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. 多环境配置

示例代码对应仓库:labx-26-sc-zookeeper-config-demo

《芋道 Spring Boot 配置文件入门》「6. 多环境配置」中,我们介绍如何基于 spring.profiles.active 配置项,在 Spring Boot 实现多环境的配置功能。

在本文的「2. 快速入门」小节中,我们已经提到,Spring Cloud ZooKeeper Config 在我们设置了 Spring Profile 的值时,会读取 Zookeeper /{root}/{spring.application.name},{profile}//{root}/{default-context},{profile}/ 目录下的节点,作为配置项。

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

3.1 创建 Zookeeper 配置项

① 创建用于 Profile 为 dev 环境的 Zookeeper /config/demo-application,dev 目录,并创建 server.port 节点的值为 8081。如下图所示:

 `/config/demo-application,dev` 目录

② 创建用于 Profile 为 prod 环境的 Zookeeper /config/demo-application,prod 目录,并创建 server.port 节点的值为 8084。如下图所示:

 `/config/demo-application,prod` 目录


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

3.2 简单测试

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

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

# 省略其它日志...
// 加载了 ZooKeeper 如下目录的配置
// 1. `/config/demo-application,dev`
// 2. `/config/demo-application`
// 3. `/config/application,dev`
// 4. `/config/application`
2020-06-09 08:44:17.338 INFO 15215 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-/config/demo-application,dev'}, BootstrapPropertySource {name='bootstrapProperties-/config/demo-application'}, BootstrapPropertySource {name='bootstrapProperties-/config/application,dev'}, BootstrapPropertySource {name='bootstrapProperties-/config/application'}]

# Tomcat 启动日志
2020-06-09 08:44:18.228 INFO 15215 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''

Tomcat 启动在 8081 端口,也符合 dev 环境读取 Zookeeper /config/demo-application,dev 目录下的配置。

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

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

# 省略其它日志...
// 加载了 ZooKeeper 如下目录的配置
// 1. `/config/demo-application,prod`
// 2. `/config/demo-application`
// 3. `/config/application,prod`
// 3. `/config/application`
2020-06-09 08:50:25.661 INFO 15375 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-/config/demo-application,prod'}, BootstrapPropertySource {name='bootstrapProperties-/config/demo-application'}, BootstrapPropertySource {name='bootstrapProperties-/config/application,prod'}, BootstrapPropertySource {name='bootstrapProperties-/config/application'}]

# Tomcat 启动日志
2020-06-09 08:50:26.532 INFO 15375 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8084 (http) with context path ''

Tomcat 启动在 8084 端口,也符合 prod 环境读取 Zookeeper /config/demo-application,prod 目录下的配置。

4. 自动刷新配置

示例代码对应仓库:labx-26-sc-zookeeper-config-auto-refresh

在上面的示例中,我们已经实现从 Zookeeper 读取配置。那么,在应用已经启动的情况下,如果我们将读取的 Zookeeper 的配置进行修改时,应用是否会自动刷新本地的配置呢?

如果是我们上面的示例,答案是部分会。使用 @ConfigurationProperties 注解的,使用 @Value 注解的不会

下面,我们从「2. 快速入门」小节的 labx-26-sc-zookeeper-config-demo 项目,复制出 labx-26-sc-zookeeper-config-auto-refresh 项目,用于演示 Zookeeper 的自动刷新配置的功能。最终项目代码如下图所示:

`labx-26-sc-zookeeper-config-auto-refresh` 项目

4.1 简单测试

① 使用 DemoApplication 启动示例应用。

获得 Zookeeper /config/demo-application 目录的配置内容为:

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

使用 curl 命令,请求 DemoController 提供的两个测试接口,过程如下:

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

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

修改 Zookeeper /config/demo-application 目录的配置内容为:

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

此时,我们可以看到 IDEA 控制台打印出了 order.create-frequency-seconds 配置项发生变化的日志,内容如下:

2020-06-09 08:30:44.793  INFO 16369 --- [tor-TreeCache-0] o.s.c.e.event.RefreshEventListener       : Refresh keys changed: [order.create-frequency-seconds]

使用 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
{"payTimeoutSeconds":60,"createFrequencySeconds":120}
  • 使用 @ConfigurationProperties 注解的成功刷新,使用 @Value 注解的失败刷新。

4.2 @RefreshScope

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

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

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

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

// ... 省略其它代码

}

4.3 重新测试

① 使用 DemoApplication 启动示例应用。

获得 Zookeeper /config/demo-application 目录的配置内容为:

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

使用 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
{"payTimeoutSeconds":60,"createFrequencySeconds":480}

修改 Zookeeper /config/demo-application 目录的配置内容为:

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

使用 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
{"payTimeoutSeconds":60,"createFrequencySeconds":960}
  • 使用 @ConfigurationProperties 注解的成功刷新,使用 @Value 注解的成功刷新。完美~

4.4 EnvironmentChangeEvent

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

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

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

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

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

下面,我们来实现一个自定义的 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));
}
}

}

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

4.5 再次测试

① 使用 DemoApplication 启动示例应用。

修改 Zookeeper /config/demo-application 目录,增加 test 节点的内容为 true。此时在 IDEA 控制台可以看到 DemoEnvironmentChangeListener 打印的日志如下:

2020-06-09 08:37:15.226  INFO 16369 --- [tor-TreeCache-0] .i.s.l.z.l.DemoEnvironmentChangeListener : [onApplicationEvent][key(test) 最新 value 为 true]

5. 配置加密

考虑到安全性,我们可能最好将配置文件中的敏感信息进行加密。例如说,MySQL 的用户名密码、第三方平台的 Token 令牌等等。不过,Zookeeper Config 暂时未内置配置加密的功能。官方文档说明如下:

因此,我们暂时只能在客户端进行配置的加解密。这里,我们继续采用在《芋道 Spring Boot 配置文件入门》「8. 配置加密」小节中使用的 Jasypt

先偷个小懒,胖友可以参考如下两篇文章实现:

666. 彩蛋

至此,我们已经完成 Spring Cloud ZooKeeper 作为配置中心的学习。如下是 Zookeeper Config 相关的官方文档:

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

文章目录
  1. 1. 1. 概述
  2. 2. 2. 快速入门
    1. 2.1. 2.1 引入依赖
    2. 2.2. 2.2 配置文件
    3. 2.3. 2.3 创建 Zookeeper 配置项
    4. 2.4. 2.4 OrderProperties
    5. 2.5. 2.5 DemoController
    6. 2.6. 2.6 DemoApplication
    7. 2.7. 2.7 简单测试
  3. 3. 3. 多环境配置
    1. 3.1. 3.1 创建 Zookeeper 配置项
    2. 3.2. 3.2 简单测试
  4. 4. 4. 自动刷新配置
    1. 4.1. 4.1 简单测试
    2. 4.2. 4.2 @RefreshScope
    3. 4.3. 4.3 重新测试
    4. 4.4. 4.4 EnvironmentChangeEvent
    5. 4.5. 4.5 再次测试
  5. 5. 5. 配置加密
  6. 6. 666. 彩蛋