[英]Snake Yaml: How to load yaml file resolving property placeholders
我们有一个 spring 启动应用程序,其配置由application.yml
文件驱动。 在此配置文件中,我们使用通过引用同一application.yml
文件中的另一个属性来定义属性的功能:
my-games-app:
base-property: foo
games:
- game-one:
game-name: ${my-games-app.base-property}one
game-location: ${my-games-app.base-property}/one
- game-two:
game-name: ${my-games-app.base-property}two
game-location: ${my-games-app.base-property}/two
我们有一个@ConfigurationProperties
bean 加载games
配置:
@Configuration
@ConfigurationProperties(prefix = "my-games-app.games")
public class GamesConfig {
private Map<String, Game> games;
...
}
不用说以上只是一个例子,实际上它是一个非常复杂的设置,其中GamesConfig
bean 被用作我们应用程序中许多其他 bean 的构造函数参数:
@Component
public class GamesRunner {
private final GamesConfig gamesConfig;
...
}
一切都按预期工作。 我们遇到的问题与测试注入GamesConfig
的 bean 有关; 在上面的示例GamesRunner
中。 目前我们使用@SpringBootTest
来获取我们想要测试的bean。 这同样可以正常工作,但主要的不便之处在于需要启动整个应用程序才能访问GamesConfig
bean。 这意味着要设置很多基础设施,例如数据库、JMS 消息代理和 Kafka 代理。 这需要时间并使我们的 CI 构建运行时间更长,这开始变得有点不方便。 因为我们要测试的 bean 除了提供GamesConfig
构造函数参数外不需要任何其他设置,所以我们更愿意进行单元测试而不是集成测试,因为它们运行起来要快得多。
换句话说,我们希望能够通过使用测试辅助方法解析我们的application.yml
来手动重新创建GamesConfig
。 为此,我们使用snakeyaml库:
public final class TestHelper {
public static GamesConfig getGamesConfig() {
var yaml = new Yaml();
var applicationYaml = (Map<String, Object>) yaml.load(readResourceAsString("application.yml");
return createGamesConfig(applicationYaml.get("games");
}
private static GamesConfig createGamesConfig(Object config) {
// The config Object passed here is a `Map<String, Map<String, String>>`
// as defeined in our `games` entry in our `application.yml`.
// The issue is that game name and game locations are loaded exactly like
// configured without property place holders being resolved
return gamesConfig;
}
}
我们通过手动解析属性占位符并在application.yml
文件中查找它们的值来解决该问题。 即使我们自己的属性占位符实现非常通用,我的感觉是不需要这项额外的工作,因为它应该是一个基本期望,库会有一些特定的设置来开箱即用。 作为 snakeyaml 的新手,我希望其他人遇到同样的问题并且知道如何去做。
我们使用 snakeyaml 是因为它恰好位于 class 路径中作为传递依赖项,我们愿意接受任何可以实现相同目的的建议。
先感谢您。
据我所知,SnakeYAML 只支持环境变量的替换,这就是为什么据我所知你想要的是不可能的。 当然,您可以做的只是简单地使用 Spring 的类,而无需设置完整的ApplicationContext
。
例如,假设您的游戏配置来自上面,您可以使用:
final var loader = new YamlPropertySourceLoader();
final var sources = loader.load(
"games-config.yml",
new ClassPathResource("games-config.yml")
);
final var mutablePropertySources = new MutablePropertySources();
sources.forEach(mutablePropertySources::addFirst);
final var resolver = new PropertySourcesPropertyResolver(mutablePropertySources);
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
System.out.println(resolver.getProperty("my-games-app.games[0].game-one.game-name"));
System.out.println(resolver.getProperty("my-games-app.games[0].game-one.game-location"));
System.out.println(resolver.getProperty("my-games-app.games[1].game-two.game-name"));
System.out.println(resolver.getProperty("my-games-app.games[1].game-two.game-location"));
输出:
fooone
foo/one
footwo
foo/two
如果您真的对 Spring 是如何做到的感兴趣,一个很好的起点是PropertySourcesPlaceholderConfigurer
class 的源代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.