「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」
一、前言
在
Spring Cloud基础上体验配置动态刷新。
场景:当配置中心的配置修改,不用重启服务就能刷新服务中的配置。
那么问题来了:
Spring Cloud下如何实现配置动态刷新?- 配置动态刷新原理是什么?
Spring Cloud 配置动态刷新的三种使用方式如下:
- 通过
Environment获取的配置:例如env.getProperty("book.category") - 被
@RefreshScope注解修饰的类:这些scope值为refresh的类初始化时经过了特殊处理,当触发RefreshEvent事件后会重新构造 - 被
@ConfigurationProperties注解修饰的配置类:这些配置类生效的原因是触发了EnvironmentChangeEvent事件
Spring Cloud 配置动态刷新机制基于事件监听机制,涉及以下两个事件:
-
RefreshEvent事件:配置刷新事件。接收到此事件后会构造一个临时的
ApplicationContext(会加上BootstrapApplicationListener和ConfigFileApplicationListener,这意味着从配置中心和配置文件重新获取配置数据)。构造完毕后,新的Environment里的PropertySource会跟原先的Environment里的PropertySource进行对比并覆盖。 -
EnvironmentChangeEvent事件:环境变化事件。接收到此事件表示应用里的配置数据已经发生改变。
EnvironmentChangeEvent事件里维护着一个配置项keys集合,当配置动态修改后,配置值发生变化后的key会设置到事件的keys集合中。
本文先来了解配置动态刷新三种方式,小结如下:
- 通过
Environment获取的配置:不能刷新@Value中的值。 - 被
@RefreshScope注解修饰的类:@RefreshScope注解和需要发送RefreshEvent得不在同一个类中,否则可能会产生死锁。 - 被
@ConfigurationProperties注解修饰的配置类:最为省事。
二、举个栗子
栗子分为四个部分:
- 实验准备
demo,用于基础对比 Environment案例@RefreshScope案例ConfigurationProperties案例
配置前要:
- 本地文件配置,例如:
bootstrap.properties
本文主要是用本地文件。
- 基于
Nacos配置文件
只是在本机文件配置上,多了一步:消息通知(从
nacos-server到nacos-client)
2021-01-24 11:34:46.152 INFO 17008 --- [168.226.36_8848] c.a.nacos.client.config.impl.CacheData : [fixed-192.168.226.36_8848] [notify-listener] time cost=348ms in ClientWorker, dataId=my-provider, group=DEFAULT_GROUP, md5=64ba151ad02c0d96b3a26483de661cf0, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1@3ea8b8a0
(1)实验准备
pom配置
<properties>
<java.version>1.8</java.version>
<spring-cloud-alibaba.version>2.2.0.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 添加代码
@RestController
class ConfigurationController {
@Value("${book.author:unknown}")
String bookAuthor;
@Value("${book.name:unknown}")
String bookName;
@Value("${book.category:unknown}")
String bookCategory;
@Autowired
ApplicationContext applicationContext;
@GetMapping("/config1")
public String config() {
return "bookAuthor=" + bookAuthor +
"<br/>bookName=" + bookName +
"<br/>bookCategory=" + bookCategory;
}
@GetMapping("/config2")
public String config2() {
return "env.get('book.author')="
+ applicationContext.getEnvironment().getProperty("book.author", "unknown")
+ "<br/>env.get('book.name')="
+ applicationContext.getEnvironment().getProperty("book.name", "unknown")
+ "<br/>env.get('book.category')="
+ applicationContext.getEnvironment().getProperty("book.category", "unknown");
}
}
bootstrap.properties配置
spring.application.name=my-provider
server.port=8080
spring.cloud.nacos.discovery.server-addr=192.168.226.36:8848
book.author=lala
book.name=springcloud
book.category=category
- 启动服务,访问
http://localhost:8080/config1和http://localhost:8080/config2,输出结果如下:
# http://localhost:8080/config1
bookAuthor=lala
bookName=springcloud
bookCategory=category
# http://localhost:8080/config2
env.get('book.author')=lala
env.get('book.name')=springcloud
env.get('book.category')=category
- 修改对应的配置
本地文件修改:
target/classes/bootstrap.properties将book.author=lala改为book.author=haha文件位置如图:
book.author=haha
book.name=springcloud
book.category=category
- 不重启服务,再次访问两个地址:
# http://localhost:8080/config1
bookAuthor=lala
bookName=springcloud
bookCategory=category
# http://localhost:8080/config2
env.get('book.author')=lala
env.get('book.name')=springcloud
env.get('book.category')=category
结果:发现修改配置文件,均未拿到最新值。
(2)Environment 案例
在上面的 ConfigurationController 中新增 event 方法,用于发送 RefreshEvent 事件:
@GetMapping("/event")
public String event() {
applicationContext.publishEvent(new RefreshEvent(this, null, "just for test"));
return "send RefreshEvent";
}
新增事件接收器用于接收 EnvironmentChangeEvent 事件:
@Component
class EventReceiver implements ApplicationListener<EnvironmentChangeEvent> {
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
System.out.println(event.getKeys());
}
}
- 配置文件
bootstrap.properties内容如下:
book.author=haha
book.name=springcloud
book.category=category
- 启动服务,访问
http://localhost:8080/event,输出结果如下:
# 控制台 Console 打印如下:
[]
- 修改配置
bootstrap.properties
book.author=jiji
book.name=springcloud
book.category=category
- 再次访问
http://localhost:8080/event,输出结果如下:
# 控制台 Console 打印如下:
[book.author]
2021-01-24 13:43:12.184 INFO 25479 --- [nio-8080-exec-7] o.s.c.e.event.RefreshEventListener : Refresh keys changed: [book.author]
# 访问 http://localhost:8080/config1
bookAuthor=haha
bookName=springcloud
bookCategory=category
# 访问 http://localhost:8080/config2
env.get('book.author')=jiji
env.get('book.name')=springcloud
env.get('book.category')=category
结果:发现配置文件修改了。
结论如下:
- 本地文件修改并触发了
RefreshEvent事件之后,Environment获取的配置是更新后的配置,配置动态刷新后生效。 - 类中的属性(
@Value修饰的)仍然是旧配置
(3)@RefreshScope 案例
问题:如何修改类中的属性呢(@Value 修饰)?
这时就需要添加一个 @RefreshScope 注解去修饰 ConfigurationController。
注意:
@RefreshScope注解和需要发送RefreshEvent得不在同一个类中,否则可能会产生死锁。
@RefreshScope
@RestController
class ConfigurationController {
...
}
只在类上增加了 @RefreshScope,其他不变。
服务启动后,访问:
# 1. 初始访问
donald@donald-pro:~$ curl localhost:8080/config1
bookAuthor=haha<br/>bookName=九阳神功<br/>bookCategory=categorydonald@donald-pro:~$
# 2. 在 nacos 里修改配置
# book.name=北冥神功
# 3. 再次访问,发现 `@Value` 的发生了变化
donald@donald-pro:~$ curl localhost:8080/config1
bookAuthor=haha<br/>bookName=北冥神功<br/>bookCategory=categorydonald@donald-pro:~$
结果:@RefreshScope 能使 @Value 中的值动态刷新。
(4)ConfigurationProperties 案例
Spring Cloud 对 @ConfigurationProperties 注解修饰的配置类做了特殊处理。
当触发 Environment ChangeEvent 事件的时候,这些配置类会进行重绑定(rebind) 以获取最新的配置。
实验步骤如下:
- 定义配置类
@Setter
@Configuration
@ConfigurationProperties(prefix = "book")
public class BookProperties {
private String category;
private String author;
private String name;
// 注意:这里需要有 setter 方法
}
初始值访问:
donald@donald-pro:~$ curl localhost:8080/config3
env.get('book.author')=haha<br/>bookAuthor=haha<br/>bookProperties=BookProperties{category='category', author='haha', name='九阳神功'}
- 修改配置
在
nacos中修改配置
book.name=北冥神功
- 运行结果如下:
donald@donald-pro:~$ curl localhost:8080/config3
env.get('book.author')=haha<br/>bookAuthor=haha<br/>bookProperties=BookProperties{category='category', author='haha', name='北冥神功'}
结果:@ConfigurationProperties 也能支持配置动态刷新。