SnakeYaml如何加载并解析含属性占位符的YAML文件
解决方案:无需启动完整Spring上下文解析带占位符的配置属性
方案1:利用Spring Boot的ConfigurationPropertySource和Binder(推荐)
Spring Boot本身内置了配置解析与绑定能力,无需启动整个应用上下文,就能复用和运行时一致的占位符解析、类型转换逻辑。
示例代码:
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import java.io.IOException; public final class TestHelper { public static GamesConfig getGamesConfig() throws IOException { // 加载application.yml为Spring可识别的PropertySource YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); PropertySource<?> propertySource = loader.load("application.yml", new ClassPathResource("application.yml")) .get(0); // 通过Binder自动绑定并解析占位符到GamesConfig类 return Binder.get(propertySource) .bind("my-games-app.games", GamesConfig.class) .orElseThrow(() -> new IllegalStateException("绑定GamesConfig失败")); } }
核心优势:
- 和应用运行时的配置解析逻辑完全一致,避免自定义实现带来的差异
- 自动处理嵌套对象、集合、Map等复杂配置结构
- 无需手动编写占位符解析逻辑,减少重复工作
方案2:SnakeYaml结合Spring的PropertyPlaceholderHelper
如果要继续使用SnakeYaml,可以借助Spring的PropertyPlaceholderHelper工具类处理占位符,不用自己实现解析逻辑。
示例代码:
import org.springframework.util.PropertyPlaceholderHelper; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; public final class TestHelper { public static GamesConfig getGamesConfig() { var yaml = new Yaml(); var applicationYaml = (Map<String, Object>) yaml.load(readResourceAsString("application.yml")); // 扁平化yml配置为键值对,用于占位符替换 Map<String, String> allProperties = flattenProperties((Map<String, Object>) applicationYaml.get("my-games-app"), "my-games-app"); // 初始化Spring占位符解析工具 PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}"); // 解析并构建GamesConfig List<Map<String, Map<String, String>>> gamesList = (List<Map<String, Map<String, String>>>) applicationYaml.get("my-games-app").get("games"); GamesConfig gamesConfig = new GamesConfig(); Map<String, Game> gamesMap = new HashMap<>(); for (Map<String, Map<String, String>> gameEntry : gamesList) { for (Map.Entry<String, Map<String, String>> entry : gameEntry.entrySet()) { String gameKey = entry.getKey(); Map<String, String> gameProps = entry.getValue(); // 解析每个属性的占位符 String gameName = helper.replacePlaceholders(gameProps.get("game-name"), allProperties::get); String gameLocation = helper.replacePlaceholders(gameProps.get("game-location"), allProperties::get); Game game = new Game(); game.setGameName(gameName); game.setGameLocation(gameLocation); gamesMap.put(gameKey, game); } } gamesConfig.setGames(gamesMap); return gamesConfig; } // 递归扁平化嵌套的配置结构 private static Map<String, String> flattenProperties(Map<String, Object> yamlMap, String prefix) { Map<String, String> result = new HashMap<>(); for (Map.Entry<String, Object> entry : yamlMap.entrySet()) { String key = prefix + "." + entry.getKey(); if (entry.getValue() instanceof Map) { result.putAll(flattenProperties((Map<String, Object>) entry.getValue(), key)); } else { result.put(key, entry.getValue().toString()); } } return result; } // 读取classpath下的资源文件 private static String readResourceAsString(String resource) { try (InputStream is = TestHelper.class.getClassLoader().getResourceAsStream(resource)) { return new String(is.readAllBytes(), StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } } }
核心优势:
- 基于已有的SnakeYaml依赖,无需额外引入大量新类
- 借助Spring成熟的占位符解析工具,避免自定义实现的漏洞
方案3:缩小Spring上下文范围
如果不想完全脱离Spring容器,但又要避免启动整个应用,可以用@ContextConfiguration只加载必要的配置类:
首先创建测试专用配置类:
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(GamesConfig.class) public class TestConfig { }
然后在测试类中使用:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = TestConfig.class) public class GamesRunnerTest { @Autowired private GamesConfig gamesConfig; // 测试逻辑直接使用gamesConfig即可 }
核心优势:
- 保留Spring依赖注入的便利性,启动速度远快于
@SpringBootTest - 自动处理配置绑定与占位符解析,无需手动编码
内容的提问来源于stack exchange,提问作者Julian
相关产品推荐
相关产品推荐

