Spring Boot运行时如何编程式读取指定profile的配置属性
Spring Boot 2.7.x 原生支持编程式读取指定profile的专属配置,不需要在应用启动时就固定激活的profile,完全可以满足Shell类应用动态根据命令参数加载对应环境配置的需求。
默认情况下Spring Boot启动时只会把当前激活profile对应的配置文件加载到上下文中,如果你启动时没有激活dev、prod这类环境,对应配置文件默认不会进入Environment,针对你要执行init --stage dev时动态读取对应配置的场景,有两种成熟的实现方式,按需选择即可。
方案1:按需加载指定profile配置(最适合交互式Shell场景)
这个方案不会修改主上下文的全局激活profile,不会导致不同命令之间的配置污染,完全匹配你执行单条命令时临时读取对应环境配置的需求。
Spring Boot 内部解析yml配置用的YamlPropertySourceLoader是公共类,可以直接拿来用,不用自己写yaml解析逻辑,也不需要额外引入依赖。
你可以写一个通用的配置加载组件,示例代码:
import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.List; @Component public class ProfileConfigLoader { private final YamlPropertySourceLoader yamlLoader = new YamlPropertySourceLoader(); /** * 读取指定profile配置文件中的属性值 * @param profile 环境标识 如dev/prod/local * @param configKey 配置键 如spring.datasource.url * @param targetType 配置值的目标类型 */ public <T> T getConfig(String profile, String configKey, Class<T> targetType) throws IOException { Resource configResource = new ClassPathResource(String.format("application-%s.yml", profile)); if (!configResource.exists()) { throw new IllegalArgumentException("对应环境的配置文件不存在: " + profile); } List<PropertySource<?>> propertySources = yamlLoader.load("customProfile:" + profile, configResource); for (PropertySource<?> source : propertySources) { Object value = source.getProperty(configKey); if (value != null) { return ConversionService.getSharedInstance().convert(value, targetType); } } return null; } }
调用方式非常简单:在init命令的处理逻辑里拿到--stage传入的环境标识后,直接调用这个组件的方法,就能拿到对应application-{stage}.yml里的任意配置值,Spring自带的类型转换工具会自动处理字符串、数字、列表等常见类型的转换,不用自己手写类型转换逻辑。
方案2:全局切换激活profile(适合单次执行的命令行应用)
如果你不止要读取配置,还要根据传入的环境参数加载对应@Profile标记的Bean,可以直接通过ConfigurableEnvironment动态修改激活的profile。
注意:这个方式会修改整个Spring上下文的全局配置,如果是常驻的交互式Shell应用不要用,会导致不同命令执行时配置互相干扰;如果是启动后只执行单条命令就退出的应用可以放心用。
核心代码示例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.stereotype.Component; @Component public class ProfileSwitcher { @Autowired private ConfigurableEnvironment environment; @Autowired(required = false) private ConfigurationPropertiesRebinder rebinder; public void switchActiveProfile(String profile) { environment.setActiveProfiles(profile); // 如果用了@ConfigurationProperties绑定配置类,需要刷新绑定值 if (rebinder != null) { rebinder.rebind(); } } }
ConfigurationPropertiesRebinder类在spring-cloud-context依赖中,Spring Boot 2.7.x 适配2021.0.x版本的Spring Cloud即可。
注意事项
- 不要自己手写Yaml解析逻辑读配置文件,Spring自带的加载器会自动处理配置覆盖、占位符解析、嵌套结构等逻辑,自己写很容易踩占位符不生效、配置优先级错乱的坑
- 如果你的profile配置里用了
spring.profiles.include引入其他配置文件,方案1的基础实现不会自动加载被引入的配置,有这种需求可以单独用SpringApplicationBuilder构建一个轻量的子上下文,指定激活对应profile后读取配置,读完直接关闭子上下文即可,不会影响主上下文 - 所有
application-*.yml都会被打包到应用的classpath下,和启动时设置的激活profile无关,不用担心运行时找不到对应配置文件的问题
内容的提问来源于stack exchange,提问作者Robert Strauch

