重启引导类 NacosConfigSampleApplication
,控制台输出如故:
[init] user name : nacos-config-sample , age : 90
再通过命令行访问 REST 资源 /user
:
% curl http://127.0.0.1:8080/user [HTTP] user name : nacos-config-sample , age : 90
如果使用沙箱环境,请直接点击应用列表
的访问
按钮,并在打开的浏览器窗口的地址栏中追加/user
,如下图:
本文中,其他的基于http访问的步骤类似,后面不在赘述
本次请求结果中的 user name 和 age 数据与应用启动时的一致,因为此时 Nacos Server 中的配置数据没变化。
随后,通过 Nacos 控制台调整 nacos-config-sample.properties 配置,将 user.age 从 90 变更为 99:
点击“发布”按钮,观察应用日志变化(部分内容被省略):
c.a.n.client.config.impl.ClientWorker : [fixed-127.0.0.1_8848] [data-received] dataId=nacos-config-sample.properties, group=DEFAULT_GROUP, tenant=null, md5=4a8cb29154adb9a0e897e071e1ec8d3c, content=user.name=nacos-config-sample user.age=99, type=properties o.s.boot.SpringApplication : Started application in 0.208 seconds (JVM running for 290.765) o.s.c.e.event.RefreshEventListener : Refresh keys changed: [user.age]
- 第 1 和 2 行代码是由 Nacos Client 输出,通知开发者具体的内容变化,不难发现,这里没有输出完整的配置内容,仅为变更部分,即配置 user.age。
- 第 3 行日志似乎让 SpringApplication 重启了,不过消耗时间较短,这里暂不解释,后文将会具体讨论,只要知道这与 Bootstrap 应用上下文相关即可。
- 最后一行日志是由 Spring Cloud 框架输出,提示开发人员具体变更的 Spring 配置 Property,可能会有多个,不过本例仅修改一处,所以显示单个。
接下来,重新访问 REST 资源 /user
:
% curl http://127.0.0.1:8080/user [HTTP] user name : nacos-config-sample , age : 99
终端日志显示了这次配置变更同步到了 @Value(“${user.age}”) 属性 userAge 的内容。除此之外,应用控制台也输出了以下内容:
[init] user name : nacos-config-sample , age : 99
而该日志是由 init()
方法输出,那么是否说明该方法被框架调用了呢?答案是肯定的。既然 @PostConstruct
方法执行了,那么 @PreDestroy
方法会不会被调用呢?不妨增加 Spring Bean 销毁回调方法:
@SpringBootApplication @RestController @RefreshScope public class NacosConfigSampleApplication { @Value("${user.name}") private String userName; @Value("${user.age}") private int userAge; @PostConstruct public void init() { System.out.printf("[init] user name : %s , age : %d%n", userName, userAge); } @PreDestroy public void destroy() { System.out.printf("[destroy] user name : %s , age : %d%n", userName, userAge); } ... }
再次重启引导类 NacosConfigSampleApplication
,初始化日志仍旧输出:
[init] user name : nacos-config-sample , age : 99
将配置 user.age 内容从 99 调整为 18,观察控制台日志变化:
c.a.n.client.config.impl.ClientWorker : [fixed-127.0.0.1_8848] [data-received] dataId=nacos-config-sample.properties, group=DEFAULT_GROUP, tenant=null, md5=e25e486af432c403a16d5fc8a5aa4ab2, content=user.name=nacos-config-sample user.age=18, type=properties o.s.boot.SpringApplication : Started application in 0.208 seconds (JVM running for 144.467) [destroy] user name : nacos-config-sample , age : 99 o.s.c.e.event.RefreshEventListener : Refresh keys changed: [user.age]
相较于前一个版本,日志插入了 destroy()
方法输出内容,并且Bean 属性 userAge 仍旧是变更前的数据 99。随后,再次访问 REST 资源 /user
,其中终端日志:
% curl http://127.0.0.1:8080/user [HTTP] user name : nacos-config-sample , age : 18
应用控制台日志:
[init] user name : nacos-config-sample , age : 18
两者与前一版本并无差异,不过新版本给出了一个现象,即当 Nacos Config 接收到服务端配置变更时,对应的 @RefreshScope
Bean 生命周期回调方法会被调用,并且是先销毁,然后由重新初始化。本例如此设计,无非想提醒读者,要意识到 Nacos Config 配置变更对 @RefreshScope
Bean 生命周期回调方法的影响,避免出现重复初始化等操作。
注:Nacos Config 配置变更调用了 Spring Cloud API
ContextRefresher
,该 API 会执行以上行为。同理,执行 Spring Cloud Acutator Endpointrefresh
也会使用ContextRefresher
。
通过上述讨论,相信读者已对 Nacos 配置变更操作相当的熟悉,后文将不再赘述相关配置。接下来继续讨论 @ConfigurationProperties
Bean 的场景。
5.2 使用 Nacos Config 实现 @ConfigurationProperties
Bean 属性动态刷新
在应用 nacos-config-sample 新增 User
类,并标注 @RefreshScope
和 @ConfigurationProperties
,代码如下:
@RefreshScope @ConfigurationProperties(prefix = "user") public class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
根据 @ConfigurationProperties
的定义, User
类的属性绑定到了配置属性前缀 user
。下一步,调整引导类,代码如下:
@SpringBootApplication @RestController @RefreshScope @EnableConfigurationProperties(User.class) public class NacosConfigSampleApplication { @Value("${user.name}") private String userName; @Value("${user.age}") private int userAge; @Autowired private User user; @PostConstruct public void init() { System.out.printf("[init] user name : %s , age : %d%n", userName, userAge); } @PreDestroy public void destroy() { System.out.printf("[destroy] user name : %s , age : %d%n", userName, userAge); } @RequestMapping("/user") public String user() { return "[HTTP] " + user; } public static void main(String[] args) { SpringApplication.run(NacosConfigSampleApplication.class, args); } }
较前一个版本 NacosConfigSampleApplication
实现,主要改动点:
- 激活
@ConfigurationProperties
Bean@EnableConfigurationProperties(User.class)
- 通过
@Autowired
依赖注入User
Bean - 使用 user Bean( toString() 方法替换
user()
中的实现
下一步,重启应用后,再将 user.age 配置从 18 调整为 99,控制台日志输出符合期望:
[init] user name : nacos-config-sample , age : 18 ...... [fixed-127.0.0.1_8848] [data-received] dataId=nacos-config-sample.properties, group=DEFAULT_GROUP, tenant=null, md5=b0f42fac52934faf69757c2b6770d39c, content=user.name=nacos-config-sample user.age=90, type=properties ...... [destroy] user name : nacos-config-sample , age : 18 o.s.c.e.event.RefreshEventListener : Refresh keys changed: [user.age]
接下来,访问 REST 资源 /user
,观察终端日志输出:
% curl http://127.0.0.1:8080/user [HTTP] User{name='nacos-config-sample', age=90}
User Bean 属性成功地变更为 90,达到实战效果。上小节提到 Nacos Config 配置变更会影响 @RefreshScope
Bean 的生命周期方法回调。同理,如果为 User
增加初始化和销毁方法的话,也会出现行文,不过本次将 User
实现 Spring 标准的生命周期接口 InitializingBean
和 DisposableBean
:
@RefreshScope @ConfigurationProperties(prefix = "user") public class User implements InitializingBean, DisposableBean { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public void afterPropertiesSet() throws Exception { System.out.println("[afterPropertiesSet()] " + toString()); } @Override public void destroy() throws Exception { System.out.println("[destroy()] " + toString()); } }
代码调整后,重启应用,并修改配置(90 -> 19),观察控制台日志输出:
[init] user name : nacos-config-sample , age : 90 ...... c.a.n.client.config.impl.ClientWorker : [fixed-127.0.0.1_8848] [data-received] dataId=nacos-config-sample.properties, group=DEFAULT_GROUP, tenant=null, md5=30d26411b8c1ffc1d16b3f9186db498a, content=user.name=nacos-config-sample user.age=19, type=properties ...... [destroy()] User{name='nacos-config-sample', age=90} [afterPropertiesSet()] User{name='nacos-config-sample', age=19} [destroy] user name : nacos-config-sample , age : 90 ...... o.s.c.e.event.RefreshEventListener : Refresh keys changed: [user.age]
不难发现, User
Bean 的生命周期方法不仅被调用,并且仍旧是先销毁,再初始化。那么,这个现象和之前看到的 SpringApplication
重启是否有关系呢?答案也是肯定的,不过还是后文再讨论。
下一小节将继续讨论怎么利用底层 Nacos 配置监听实现 Bean 属性动态刷新