Spring Boot运行时如何通过Controller修改yaml文件占位符值
Spring Boot运行时动态修改application.yml配置实现
首先修正你提供的application.yml的语法错误:标准yaml要求键值对为
key: value结构,你当前写的配置缺少值对应的键名,会导致解析失败,修正后的示例如下:
resource: value: KL-MED123-DEV # 需根据用户响应修改为不同值 client: value: CLI-AR234 # 需根据用户响应修改为不同值
以下实现同时满足配置持久化到文件、运行时立即生效、支持其他配置项引用新值的要求。
1. 基础配置
先配置外部yaml加载路径,避免jar部署时无法写入内部文件的问题:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.io.FileSystemResource; @Configuration public class YamlConfig { // 固定读取jar包同级config目录下的application.yml public static final String YAML_PATH = System.getProperty("user.dir") + "/config/application.yml"; @Bean public static PropertySourcesPlaceholderConfigurer properties() { PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer(); configurer.setLocation(new FileSystemResource(YAML_PATH)); return configurer; } }
2. Yaml读写工具类
封装yaml的读取、写入、嵌套键修改逻辑,Spring Boot已内置SnakeYaml依赖,无需额外引入:
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import java.io.*; import java.util.HashMap; import java.util.Map; public class YamlUtil { public static Map<String, Object> loadYaml(String path) throws IOException { DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); try (InputStream in = new FileInputStream(path)) { return yaml.load(in); } } public static void writeYaml(String path, Map<String, Object> content) throws IOException { DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); options.setPrettyFlow(true); Yaml yaml = new Yaml(options); try (Writer writer = new FileWriter(path)) { yaml.dump(content, writer); } } @SuppressWarnings("unchecked") public static void setValue(Map<String, Object> map, String keyPath, Object value) { String[] keys = keyPath.split("\\."); Map<String, Object> current = map; for (int i = 0; i < keys.length - 1; i++) { current = (Map<String, Object>) current.getOrDefault(keys[i], new HashMap<>()); } current.put(keys[keys.length - 1], value); } }
3. Controller层实现逻辑
修改文件后同步刷新Spring内存中的配置源,保证配置立即生效:
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Properties; @RestController public class PingController { private final ConfigurableEnvironment environment; public PingController(ConfigurableEnvironment environment) { this.environment = environment; } @GetMapping("/ping") public ResponseEntity<String> ping(@RequestParam String resourceVal, @RequestParam String clientVal) throws IOException { String yamlPath = YamlConfig.YAML_PATH; // 读取现有配置 Map<String, Object> yamlContent = YamlUtil.loadYaml(yamlPath); // 更新目标配置值,key路径根据实际yml结构调整 YamlUtil.setValue(yamlContent, "resource.value", resourceVal); YamlUtil.setValue(yamlContent, "client.value", clientVal); // 写入文件持久化,保证应用重启后配置不丢失 YamlUtil.writeYaml(yamlPath, yamlContent); // 刷新内存配置源,让新值立即生效 MutablePropertySources propertySources = environment.getPropertySources(); Properties props = new Properties(); props.putAll(flattenMap(yamlContent, "")); String sourceName = "applicationConfig: [file:" + yamlPath + "]"; propertySources.replace(sourceName, new PropertiesPropertySource(sourceName, props)); return ResponseEntity.ok(String.format("配置更新成功,当前resource=%s,client=%s", environment.getProperty("resource.value"), environment.getProperty("client.value"))); } // 嵌套map打平为Spring配置兼容的一维key-value结构 @SuppressWarnings("unchecked") private Map<String, Object> flattenMap(Map<String, Object> source, String parentKey) { Map<String, Object> result = new HashMap<>(); for (Map.Entry<String, Object> entry : source.entrySet()) { String key = parentKey.isEmpty() ? entry.getKey() : parentKey + "." + entry.getKey(); Object value = entry.getValue(); if (value instanceof Map) { result.putAll(flattenMap((Map<String, Object>) value, key)); } else { result.put(key, value); } } return result; } }
接口调用示例:/ping?resourceVal=KL-MED456-PROD&clientVal=CLI-XY987,传入的参数即为要更新的目标配置值。
注意事项
- 若采用jar包部署,禁止修改jar包内部打包的application.yml文件,必须将yml放在jar包同级的
config目录下加载外部配置,jar包运行时内部文件为只读状态,直接写入会抛出IO异常。 - 若项目集成了Spring Cloud,只需给所有依赖配置值的Bean添加
@RefreshScope注解,配置修改后调用上下文的refresh()方法即可自动刷新所有注入的配置值,无需手动替换PropertySource。 - 配置写入逻辑需要加分布式锁或本地同步锁,避免多请求并发写入导致yaml文件格式损坏。
- 只要内存中的配置源完成刷新,yaml中其他引用这两个值的占位符会自动读取最新值,无需额外处理引用关系。
内容的提问来源于stack exchange,提问作者Rahul Maurya
相关产品推荐
相关产品推荐

