繁体   English   中英

Snake Yaml:如何加载 yaml 文件解析属性占位符

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM