背景
springboot单体项目在运行过程需要刷新springboot配置文件值,比如某个接口限流阈值,新增某个账户等场景。分布式设计的可以直接引入一些持久化中间件比如redis等,也可以用相关配置中心中间件如nacos等。处于成本等场景单体项目可以考虑①把配置文件缓存到java对象中,然后暴露出刷新配置文件接口②本文主要讲如何利用spring-cloud-context组件快速实现手动更新配置文件。
原理
依赖
注意不同springboot版本和springcloud要对应https://spring.io/projects/spring-cloud#overview
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<!--注意不同springboot版本和cloud要对应https://spring.io/projects/spring-cloud#overview-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
</dependencies>
配置文件
application.properties
server.refresh=${random.long}
server.key=${random.long}
config.uuid=${random.uuid}
代码
演示@ConfigurationProperties和@value方式引入的两种方式读取配置文件的刷新
注意@value方式引入需要在对应类上添加@RefreshScope注解
ServerConfig.java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "server")
@Data
public class ServerConfig {
private String key;
private Long refresh;
}
ValueConfig.java
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@RefreshScope// @Value这种方式必须标注此注解contextRefresher.refresh()后才会生效
@Component
@Data
public class ValueConfig {
@Value("${config.uuid}")
private String uuid;
}
测试接口DemoController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Set;
@RestController
public class DemoController {
@Autowired
private ContextRefresher contextRefresher;
@Autowired
private ServerConfig serverConfig;
@Autowired
private ValueConfig valueConfig;
@GetMapping(path = "/show")
public Object show() {
// 取得的值不变
return "serverConfig:"+serverConfig+" valueConfig:"+valueConfig;
}
@GetMapping(path = "/refresh")
public Object refresh() {
// 刷新后将获取到刷新后的值 ,包括新增key,注意开发模式运行服务后编译后的文件路径,一般是在target
new Thread(() -> {
contextRefresher.refresh();
}).start();
return show();
}
}
测试
访问/refresh接口可以看到每次获取的值不一样,当然也可以手动更新配置文件的值(更新v、新增k-v、删除k-v),注意开发模式手动更改是在编译后的文件路径,一般是在target下
serverConfig:ServerConfig(key=8733164433754704077, refresh=5118825739210703127) valueConfig:ValueConfig(uuid=ca7bceba-66ef-4ea4-a4f8-6caa027927f3)
拓展
如果想要配置文件更新后做一些其它动作,比如邮件通知下牛马开发员,从可以从contextRefresher.refresh()源码得知配置文件发送变更后会触发this.context.publishEvent,利用spring的事件通知机制就可以实现了。了解更多@EventListener注解知识可以参考博客[https://blog.csdn.net/u012060033/article/details/136006082]
@Configuration
public class EnvConfiguration{
@EventListener
public void envListener(EnvironmentChangeEvent event) {
// 配置文件刷新会触发此事件,key的变化可以通过event.getKeys()获取
System.out.println("conf change: " + event);
}
}