项目 nacos-property-spring-boot-refresher 通过 Nacos
实现 Property
和 自定义 Scope
类型 Bean
的动态刷新。
示例项目 nacos-property-refresher-spring-boot-starter-examples
众所周知:
-
Nacos
在Spring Cloud
(the"SC"
) 体系下, 实现了SC
的标准规范, 天然支持自动刷新。 -
Nacos
在Spring Boot
(the"SB"
) 其实也是支持自动刷新的。
那为什么还要自己实现呢?
原因:
根据个人的实践经验(可能不正确)。
Nacos
其实自动刷新的是上下文的环境变量org.springframework.core.env.Environment
;- 也就是不刷新
Property
的Bean
; - 通过
Environment#getProperty
是可以动态感知的。
- 也就是不刷新
- 通过
@Value("${a.b.c.....z}")
@org.springframework.beans.factory.annotation.Autowired
等注解实现依赖注入的并不会动态刷新。
1.怎么使用
1.1.版本号
httphttps://central.sonatype.com/artifact/io.github.photowey/nacos-property-refresher-spring-boot-starter
xml
<!-- ${nacos-property-refresher-starter.version} == ${latest.version} -->
<dependency>
<groupId>io.github.photowey</groupId>
<artifactId>nacos-property-refresher-spring-boot-starter</artifactId>
<version>${nacos-property-refresher-starter.version}</version>
<type>pom</type>
</dependency>
2.APIs
2.1.DataIds
${spring.application.name}
${spring.application.name}-dev
${spring.application.name}-app
...
2.2.Properties
2.2.1.AppProperties
java
@Data
//@ConfigurationProperties(prefix = "io.github.photowey.dynamic.property")
public class AppProperties {
private Cache cache = new Cache();
@Data
public static class Cache implements Serializable {
// ${io.github.photowey.dynamic.property.cache.loader}
private String loader = "local";
// ${io.github.photowey.dynamic.property.cache.expired}
private long expired = TimeUnit.MINUTES.toMillis(5);
// ${io.github.photowey.dynamic.property.cache.unit}
private TimeUnit unit = TimeUnit.MILLISECONDS;
}
public static String getPrefix() {
return "io.github.photowey.dynamic.property";
}
}
2.2.2.HelloProperties
java
@Data
//@ConfigurationProperties(prefix = "io.github.photowey.static.property")
public class HelloProperties {
private Cache cache = new Cache();
@Data
public static class Cache implements Serializable {
// ${io.github.photowey.static.property.cache.loader}
private String loader = "local";
// ${io.github.photowey.static.property.cache.expired}
private long expired = TimeUnit.MINUTES.toMillis(5);
// ${io.github.photowey.static.property.cache.unit}
private TimeUnit unit = TimeUnit.MILLISECONDS;
}
public static String getPrefix() {
return "io.github.photowey.static.property";
}
}
2.3.Configuration
java
// 示例配置 -> 其他方式均可, 核心思想: 被注解 @NacosDynamicRefreshScope 修饰
@Configuration
public class DynamicPropertyConfigure {
// ...
@Bean
@NacosDynamicRefreshScope // 添加 Nacos 动态刷新注解 -> 类似于 SC 的 @RefreshScope
public AppProperties appProperties(Environment environment) {
return PropertyBinders.bind(environment, AppProperties.getPrefix(), AppProperties.class);
}
@Bean
@NacosDynamicRefreshScope
public HelloProperties helloProperties(Environment environment) {
return PropertyBinders.bind(environment, HelloProperties.getPrefix(), HelloProperties.class);
}
// ...
}
2.4.Beans
java
@RestController
@RequestMapping("/api/v1/scope")
@NacosDynamicRefreshScope // 添加动态刷新注解
public class ScopeApiController {
// ...
}
3.核心
3.1.监听器
AbstractNacosDynamicRefreshListener
// 已经实现了大部分动态刷新需要的功能开发者:
- 1.定义需要监听
data-id
列表DYNAMIC_DATA_IDS
- 2.内部会根据实际情况调用两次刷新
- 接收到
Nacos
的固有事件NacosConfigReceivedEvent
- 接收到
Nacos
的笔变更事件ConfigChangeEvent
- 当出现
json
解析错误的情况,可能不会触发,如果也期望触发,可能需要结合NacosConfigReceivedEvent
实现
- 重写
preRefresh
方法,并返回 true.- 3.通过监听器添加对
data-id
的监听
addListener
addTemplateListener
- 支持
${spring.application.name}
这样的占位符- 也就是通过
{}-x
占位, 会自动解析成${spring.application.name}-x
对应的值.
java
// @Component || @Bean
public class HelloDynamicNacosConfigListener extends AbstractNacosDynamicRefreshListener {
// {} -> ${spring.application.name}
// Register the dataid list that needs to be refreshed dynamically.
private static final List<String> DYNAMIC_DATA_IDS = Lists.newArrayList(
"{}-app"
);
@Override
public void registerListener(Collection<ConfigService> configServices) {
for (ConfigService configService : configServices) {
DYNAMIC_DATA_IDS.forEach(dataIdTemplate -> this.addTemplateListener(configService, dataIdTemplate));
}
}
// ...this#addListener
// protected boolean preRefresh(NacosConfigReceivedEvent event) {}
// protected void posRefresh(NacosConfigReceivedEvent event) {}
// protected boolean preRefresh(ConfigChangeEvent event) {}
// protected void posRefresh(ConfigChangeEvent event) {}
// protected boolean determineHandleNacosConfigReceivedEvent(NacosConfigReceivedEvent event) {}
// protected boolean determineHandleConfigChangeEvent(ConfigChangeEvent event) {}
}
4.测试
4.1.Static
ApiController
不动态刷新
java
@RestController
@RequestMapping("/api/v1")
public class ApiController {
public static String DYNAMIC_KEY = "io.github.photowey.dynamic.property.cache.loader";
public static String STATIC_KEY = "io.github.photowey.static.property.cache.loader";
@Autowired
private Environment environment;
@Value("${io.github.photowey.dynamic.property.cache.loader}")
private String dynamicLoader;
@Value("${io.github.photowey.static.property.cache.loader}")
private String staticLoader;
@Autowired
private AppProperties appProperties;
@Autowired
private HelloProperties helloProperties;
@Autowired
private ApplicationContext applicationContext;
/**
* http://localhost:9527/api/v1/static/get/dataid/dev
*
* @return {@link DynamicValuesDTO}
*/
@GetMapping("/static/get/dataid/dev")
public ApiResult<DynamicValuesDTO> dev() {
DynamicValuesDTO dto = DynamicValuesDTO.builder()
.value(this.staticLoader)
.environment(this.tryAcquireLoaderFromEnvironment(STATIC_KEY))
.property(this.helloProperties.getCache().getLoader())
.ctxProperty(this.applicationContext.getBean(HelloProperties.class).getCache().getLoader())
.build();
return ApiResult.ok(dto);
}
/**
* http://localhost:9527/api/v1/dynamic/get/dataid/app
*
* @return {@link DynamicValuesDTO}
*/
@GetMapping("/dynamic/get/dataid/app")
public ApiResult<DynamicValuesDTO> app() {
DynamicValuesDTO dto = DynamicValuesDTO.builder()
.value(this.dynamicLoader)
.environment(this.tryAcquireLoaderFromEnvironment(DYNAMIC_KEY))
.property(this.appProperties.getCache().getLoader())
.ctxProperty(this.applicationContext.getBean(AppProperties.class).getCache().getLoader())
.build();
return ApiResult.ok(dto);
}
private String tryAcquireLoaderFromEnvironment(String key) {
return this.environment.getProperty(key);
}
}
4.2.Dynamic
ScopeApiController
被@NacosDynamicRefreshScope
注解修饰, 会自动刷新
@Value
@Autowired
- 均会自动刷新
java
@RestController
@RequestMapping("/api/v1/scope")
@NacosDynamicRefreshScope
public class ScopeApiController {
public static String DYNAMIC_KEY = "io.github.photowey.dynamic.property.cache.loader";
public static String STATIC_KEY = "io.github.photowey.static.property.cache.loader";
@Autowired
private Environment environment;
@Value("${io.github.photowey.dynamic.property.cache.loader}")
private String dynamicLoader;
@Value("${io.github.photowey.static.property.cache.loader}")
private String staticLoader;
@Autowired
private AppProperties appProperties;
@Autowired
private HelloProperties helloProperties;
@Autowired
private ApplicationContext applicationContext;
/**
* http://localhost:9527/api/v1/scope/static/get/dataid/dev
*
* @return {@link DynamicValuesDTO}
*/
@GetMapping("/static/get/dataid/dev")
public ApiResult<DynamicValuesDTO> dev() {
DynamicValuesDTO dto = DynamicValuesDTO.builder()
.value(this.staticLoader)
.environment(this.tryAcquireLoaderFromEnvironment(STATIC_KEY))
.property(this.helloProperties.getCache().getLoader())
.ctxProperty(this.applicationContext.getBean(HelloProperties.class).getCache().getLoader())
.build();
return ApiResult.ok(dto);
}
/**
* http://localhost:9527/api/v1/scope/dynamic/get/dataid/app
*
* @return {@link DynamicValuesDTO}
*/
@GetMapping("/dynamic/get/dataid/app")
public ApiResult<DynamicValuesDTO> app() {
DynamicValuesDTO dto = DynamicValuesDTO.builder()
.value(this.dynamicLoader)
.environment(this.tryAcquireLoaderFromEnvironment(DYNAMIC_KEY))
.property(this.appProperties.getCache().getLoader())
.ctxProperty(this.applicationContext.getBean(AppProperties.class).getCache().getLoader())
.build();
return ApiResult.ok(dto);
}
private String tryAcquireLoaderFromEnvironment(String key) {
return this.environment.getProperty(key);
}
}
4.3.启动
4.3.1.示例接口
http
http://localhost:9527/api/v1/scope/static/get/dataid/dev
http
http://localhost:9527/api/v1/scope/dynamic/get/dataid/app
4.3.2.修改 Nacos
当修改 data-id
的值之后, 再次访问即可看到差异。
4.3.3.数据结构
json
{
"code": "200",
"message": "OK",
"data": {
"value": "database",
"environment": "database",
"property": "database",
"ctxProperty": "database"
}
}
5.总结
5.1.核心思想
- 模仿
SC
的@RefreshScope
实现 - Spring
Scope
bean 类型 - 监听
Nacos
对应data-id
的变更.