SpringBoot不使用配置中心实现本地配置文件定时动态刷新
由于还没有接入nacos配置中心,所以需要出一个版本更新本地动态刷新配置的功能,实现配置的动态更新。经过研究发现SpringCloud已经提供了手动接口/actuator/refresh方式刷新的功能,这里再进行处理下实现定时任务动态刷新配置。
使用版本
SpringBoot版本2.3.10.RELEASE
1 2 3 4 5 6 7 8 9 10 11 12
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.10.RELEASE</version> <relativePath/> </parent>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>2.2.8.RELEASE</version> </dependency>
|
首先集成SpringCloud中的refresh刷新功能
上面已经添加了所有的依赖包。
依赖spring-cloud-starter-config来实现配置更新,所以必须要有
yml配置
application.yml配置文件添加配置test.version, 必须要添加endpoints refresh对外暴露接口
1 2 3 4 5 6 7 8 9 10 11 12 13
| server: port: 8011 undertow: threads: io: 10 worker: 40 management: endpoints: web: exposure: include: health,info,prometheus,refresh test: version: 1.0.0
|
@RefreshScope注解
在配置获取参数的地方加上@RefreshScope注解,必须要加这个注解才能够实现配置刷新。(@Data是Lombok注解)
1 2 3 4 5 6 7 8
| @Data @RefreshScope @Component("propertyConfig") public class PropertyConfig {
@Value("${test.version:false}") private String testVersion; }
|
添加controller
1 2 3 4 5 6 7 8 9 10 11 12
| @Slf4j @RestController public class VersionController {
@Resource private PropertyConfig propertyConfig;
@RequestMapping(value = {"/test/version"}, method = RequestMethod.GET) public String testVersion() { return propertyConfig.getTestVersion(); } }
|
刷新步骤和现象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 1. 浏览器或着postman调用 http://localhost:8011/test/version接口。 返回结果:1.0.0
2. application.yml配置文件修改 test: version: 2.0.0
3. post方式 调用 http://localhost:8011/actuator/refresh, 注意这里一定是post。 请求会返回已经被刷新的配置key列表: [ "test.version" ] 4. 重复1的逻辑,调用 http://localhost:8011/test/version接口。 返回结果:2.0.0
|
注意问题
http://localhost:8011/actuator/refresh 接口必须要用post方式调用。
- 比如在idea中运行,修改的配置文件一定是编译目录下的yml配置文件,一般在idea中是橘黄色标识 target/classes下的application.yml
分析refresh的实现
找到/actuator/refresh接口的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Endpoint(id = "refresh") public class RefreshEndpoint {
private ContextRefresher contextRefresher;
public RefreshEndpoint(ContextRefresher contextRefresher) { this.contextRefresher = contextRefresher; }
@WriteOperation public Collection<String> refresh() { Set<String> keys = this.contextRefresher.refresh(); return keys; }
}
|
- 发现就是使用 contextRefresher.refresh() 方法实现刷新的。所以我们可以利用这个方法来自己控制什么时候动态刷新配置。
- 感兴趣可以深入看一下源码内部实现。
集成schedule任务定时刷新
开启schedule
Application上加上@EnableScheduling注解
1 2 3 4 5
| @EnableScheduling @SpringBootApplication public class Application { // ...... }
|
定时刷新配置任务代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import java.io.File; import java.util.Set;
@RefreshScope @Component public class PropertiesRefreshTask { private static final Logger log = LoggerFactory.getLogger(PropertiesRefreshTask.class); /** * 文件更新时间 */ private long fileUpdateTime = 0L; /** * 这里是配置文件的目录 */ @Value("${yml.path:/usr/local/lark}") private String ymlPath;
@Resource private ContextRefresher contextRefresher;
/** * 30s检查一次 */ @WriteOperation @Scheduled(fixedDelay = 30000L) public void propertiesRefreshTask() { try { File file = new File(ymlPath + "/application.yml"); // 检测文件是否存在 if (!file.exists()) { log.warn("application.yml not exist"); return; } long lastModifiedTime = file.lastModified(); // 文件更新时间和记录对比 if (fileUpdateTime == 0 || fileUpdateTime >= lastModifiedTime) { fileUpdateTime = lastModifiedTime; return; } Set<String> keys = contextRefresher.refresh(); log.info("propertiesRefresh keys={}", keys); } catch (Exception e) { log.error("propertiesRefresh exception={}", e.getMessage(), e); } }
}
|
刷新步骤和现象
1 2 3 4 5 6 7 8 9 10 11
| 1. 浏览器或着postman调用 http://localhost:8011/test/version接口 返回结果:1.0.0
2. application.yml配置文件修改 test: version: 2.0.0
3. 等待30s时间 4. 重复1的逻辑,调用 http://localhost:8011/test/version接口 返回结果:2.0.0
|
总结
- 这个是没有接入配置中心导致这么使用的原因,还是建议使用配置中心来实现配置动态加载。
- 后续会从源码角度分析下SrpingCloud的refresh的实现逻辑及作用范围。