RefeshScope 简易剥析
@RefreshScope详解
Springboot 使用@RefreshScope 注解,实现配置文件的动态加载
简易应用:
1、添加@RefreshScope注解
如果是使用@Value("${test.message}"),在引用类上添加注解
@Slf4j
@RefreshScope
@RestController
@RequestMapping("/test")
public class TestController {
@Value("${test.message}") //引入配置
private String message;
@GetMapping("/testMethod")
public Response<List<Map<String, String>>> testMethod() {
log.info("message:" + message);
return Response.success(message);
}
}
如果是使用自定义注解配置类@ConfigurationProperties(prefix = "test"),在配置类添加注解,使用类直接引入
@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "test")
public class ErrorMsgConfigProperties {
/**
* 转化异常列表
*/
private List<Map<String, String>> errorMsgList;
}
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private ErrorMsgConfigProperties errorMsgConfigProperties;
@GetMapping("/testMethod")
public Response<List<Map<String, String>>> testMethod() {
List<Map<String, String>> errorMsgList = errorMsgConfigProperties.getErrorMsgList();
return Response.success(errorMsgList);
}
}
2、引入必要依赖和配置
<!-- 使用@RefreshScope注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.6.0</version>
</dependency>
<!-- 使用actuator实现手动刷新 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>3.0.4</version>
</dependency>
application.yml添加配置项
#注意2.0.3版本后暴露/refresh接入点的方式与旧版本不同,需要手动设置暴露点。
management:
endpoints:
web:
exposure:
include: "*" #默认值暴露 /actuator/info和/actuator/health,没有暴露/actuator/bus-refresh,这里把所有endpoints都暴露
# server:
# port: 1234 #actuator的端口可以单独定义,如果不定义的话,默认为{server.port}
3、运行项目,检验结果
推荐使用外部依赖jar启动(将application.yml文件置于启动jar的同等级目录),查看直观且方便修改,随后启动: jar -jar xxxx.jar
初步运行 localhost:9232/api/v1/test/testMethod,结果为:
{
"code": "0",
"message": "success",
"requestId": null,
"data": [
{
"code": "default",
"message": "服务异常,请稍后重试!"
}
]
}
接着修改配置文件内容(注,该配置文件为编译后文件,使用编译器的话,在target下;使用命令打包后启动的话,在classes下),直接打开外部依赖文件,修改内容后,
运行刷新链接:localhost:9232/api/v1/actuator/refresh ,
因为配置了项目链接前缀,没配置的 请求: localhost:9232/actuator/refresh
[
"test.info[0].message"
]
刷新结果会展示出你修改的参数,把涉及修改后差异的参数名返回,流程可参看顶部的“@RefreshScope详解”
现在再运行请求 localhost:9232/api/v1/test/testMethod,结果为:
{
"code": "0",
"message": "success",
"requestId": null,
"data": [
{
"code": "default",
"message": "服务异常,请稍后重试!123123"
}
]
}
刷新结果实现!
4、扩展实践
通过接口修改配置文件内容并使其生效,demo如下:
@Slf4j
@RefreshScope
@RestController
@RequestMapping(value = "test")
public class TestController {
@Autowired
private ObjectButton objectButton;
@GetMapping(value = "/getButtonValue")
public String getButtonValue() {
return "此时的buttonStr:" + objectButton.getButtonStr();
}
@GetMapping(value = "/updateButtonValue")
public String updateButtonValue(String value) {
System.out.println("修改前:" + objectButton.getButtonStr());
String fileName = "application.properties";
Map<String, String> keyValueMap = new HashMap<>();
keyValueMap.put("open.buttonStr", value);
updateProperties(fileName, keyValueMap);
return "此时的buttonStr:" + objectButton.getButtonStr();
}
/**
* 传递键值对的Map,更新properties文件
*
* @param fileName 文件名(放在resource源包目录下),需要后缀
* @param keyValueMap 键值对Map
*/
public void updateProperties(String fileName, Map<String, String> keyValueMap) {
Map<String, String> newkeyValueMap = new HashMap<>();
//getResource方法使用了utf-8对路径信息进行了编码,当路径中存在中文和空格时,他会对这些字符进行转换,这样,
//得到的往往不是我们想要的真实路径,在此,调用了URLDecoder的decode方法进行解码,以便得到原始的中文及空格路径。
String filePath = PropertiesUtil.class.getClassLoader().getResource(fileName).getFile();
Properties props = null;
BufferedWriter bw = null;
try {
filePath = URLDecoder.decode(filePath, "utf-8");
log.debug("updateProperties propertiesPath:" + filePath);
props = PropertiesLoaderUtils.loadProperties(new ClassPathResource(fileName));
for(Object key:props.keySet()){
newkeyValueMap.put(key.toString() , props.get(key).toString());
}
log.debug("updateProperties old:" + props);
// 写入属性文件
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath)));
props.clear();// 清空旧的文件
//将新值覆盖掉原有的
for (String key : keyValueMap.keySet()) {
newkeyValueMap.put(key, keyValueMap.get(key));
}
//将所有新值写入配置文件
for (String key : newkeyValueMap.keySet()) {
System.out.println(key + ":" + newkeyValueMap.get(key));
props.setProperty(key, newkeyValueMap.get(key));
}
log.debug("updateProperties new:" + props);
props.store(bw, "");
} catch (IOException e) {
log.error(e.getMessage());
} finally {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 直接内部请求刷新配置,虽然会生效,不过会报timeout
// String url = "http://localhost:9232/api/v1/actuator/refresh" ;
// try {
// String result = HttpUtil.doPost(url, "{}");
// log.info("result : " + result);
// return;
// } catch (IOException e) {
// e.printStackTrace();
// }
}
}
配置文件读取和修改参考:java 4种方式读取配置文件 + 修改配置文件
调用修改接口后,访问 localhost:9232/api/v1/actuator/refresh,实现动态刷新。
会有人反馈为啥不存储数据库,一步到位,流程也好操作?!
这是根据业务场景来,根据实际场景使用。如果是应用于集群环境,需自行修改配置