背景
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 {@Autowiredprivate ContextRefresher contextRefresher;@Autowiredprivate ServerConfig serverConfig;@Autowiredprivate ValueConfig valueConfig;@GetMapping(path = "/show")public Object show() {// 取得的值不变return "serverConfig:"+serverConfig+" valueConfig:"+valueConfig;}@GetMapping(path = "/refresh")public Object refresh() {// 刷新后将获取到刷新后的值 ,包括新增key,注意开发模式运行服务后编译后的文件路径,一般是在targetnew 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]
@Configurationpublic class EnvConfiguration{@EventListenerpublic void envListener(EnvironmentChangeEvent event) {// 配置文件刷新会触发此事件,key的变化可以通过event.getKeys()获取System.out.println("conf change: " + event);}}