You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 15:10:39