Spring配置文件可以选择@PropertySources吗?
Posted
技术标签:
【中文标题】Spring配置文件可以选择@PropertySources吗?【英文标题】:Can @PropertySources be chosen by Spring profile? 【发布时间】:2012-09-23 09:30:49 【问题描述】:我有一个 Spring 3.1 @Configuration
需要一个属性 foo
来构建一个 bean。该属性在defaults.properties
中定义,但如果应用程序具有活动的override
Spring 配置文件,则可能会被overrides.properties
中的属性覆盖。
如果没有覆盖,代码将如下所示,并且可以工作...
@Configuration
@PropertySource("classpath:defaults.properties")
public class MyConfiguration
@Autowired
private Environment environment;
@Bean
public Bean bean()
...
// this.environment.getRequiredProperty("foo");
...
我想要一个@PropertySource
用于classpath:overrides.properties
,取决于@Profile("overrides")
。有没有人对如何实现这一点有任何想法?我考虑过的一些选项是重复的 @Configuration
,但这会违反 DRY 或对 ConfigurableEnvironment
的编程操作,但我不确定 environment.getPropertySources.addFirst()
调用会去哪里。
如果我直接使用 @Value
注入属性,则将以下内容放在 XML 配置中有效,但当我使用 Environment
和 getRequiredProperty()
方法时则无效。
<context:property-placeholder ignore-unresolvable="true" location="classpath:defaults.properties"/>
<beans profile="overrides">
<context:property-placeholder ignore-unresolvable="true" order="0"
location="classpath:overrides.properties"/>
</beans>
更新
如果您现在尝试这样做,请查看 Spring Boot 的 YAML support,尤其是“使用 YAML 代替属性”部分。那里的配置文件支持会让这个问题变得毫无意义,但还没有@PropertySource
支持。
【问题讨论】:
【参考方案1】:这里提到的所有解决方案都有些尴尬,只适用于一个配置文件预设,它们不适用于更多/其他配置文件。目前有一个 Spring 团队 refuses 来介绍这个特性。但这是我发现的解决方法:
package com.example;
public class MyPropertySourceFactory implements PropertySourceFactory, SpringApplicationRunListener
public static final Logger logger = LoggerFactory.getLogger(MyPropertySourceFactory.class);
@NonNull private static String[] activeProfiles = new String[0];
// this constructor is used for PropertySourceFactory
public MyPropertySourceFactory()
// this constructor is used for SpringApplicationRunListener
public MyPropertySourceFactory(SpringApplication app, String[] params)
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment)
activeProfiles = environment.getActiveProfiles();
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException
logger.info("Loading: with profiles: ", encodedResource.toString(), activeProfiles);
// here you know all profiles and have the source Resource with main
// properties, just try to load other resoures in the same path with different
// profile names and return them as a CompositePropertySource
要使其正常工作,您必须拥有具有以下内容的 src/main/resources/META-INF/spring.factories
:
org.springframework.boot.SpringApplicationRunListener=com.example.MyPropertySourceFactory
现在您可以将自定义属性文件放在某处并使用@PropertySources
加载它:
@Configuration
@PropertySource(value = "classpath:lib.yml", factory = MyPropertySourceFactory.class)
public class PropertyLoader
【讨论】:
【参考方案2】:我建议,定义两个文件,其中第二个是可选的,配置文件作为后缀:
@Configuration
@PropertySources(
@PropertySource("classpath:/myconfig.properties"),
@PropertySource(value = "classpath:/myconfig-$spring.profiles.active.properties", ignoreResourceNotFound = true)
)
public class MyConfigurationFile
@Value("$my.prop1")
private String prop1;
@Value("$my.prop2")
private String prop2;
【讨论】:
对我来说这部分很神奇:$spring.profiles.active
,现在我可以有两个文件:config-dev.properties
和 config-prod.properties
,java 类有下一行 @PropertySource("classpath:config-$spring.profiles.active.properties")
如果您有多个配置文件(例如,我有活动配置文件 = "dev,mock"
我很幸运能找到这个答案。简单明了。你救了我。【参考方案3】:
如果您需要支持多个配置文件,您可以执行以下操作:
@Configuration
public class Config
@Configuration
@Profile("default")
@PropertySource("classpath:application.properties")
static class DefaultProperties
@Configuration
@Profile("!default")
@PropertySource("classpath:application.properties", "classpath:application-$spring.profiles.active.properties")
static class NonDefaultProperties
这样您就不需要为每个配置文件定义一个静态配置类。 感谢David Harkness 让我朝着正确的方向前进。
【讨论】:
多个活动配置文件失败 - 你最好只创建单独的 @Profile 设置【参考方案4】:在静态内部类中添加覆盖 @PropertySource
。不幸的是,您必须同时指定所有属性源,这意味着创建“默认”配置文件来替代“覆盖”。
@Configuration
public class MyConfiguration
@Configuration
@Profile("default")
@PropertySource("classpath:defaults.properties")
static class Defaults
@Configuration
@Profile("override")
@PropertySource("classpath:defaults.properties", "classpath:overrides.properties")
static class Overrides
// nothing needed here if you are only overriding property values
@Autowired
private Environment environment;
@Bean
public Bean bean()
...
// this.environment.getRequiredProperty("foo");
...
【讨论】:
不明白为什么这个答案得到了这么多的支持。对配置文件名称进行硬编码是违反配置文件的。是不是有类似的方式让你通过'spring.profiles.active'参数指定profile? 今天您可以使用配置文件特定的属性文件。我不知道在写完这个答案时这是否已经成为可能(看here)。 @Fencer 只有在使用 Spring Boot 时才可用。 @NimaAJ 这可能是真的,但由于 jjoller 指的是“spring.profiles.active”,因此对于那些因他的问题而绊倒的人来说,我的提示可能是一个有价值的信息。可能使用“spring.profiles.active”暗示使用 Spring Boot 或使用配置文件特定属性文件的可能性。不过,我应该添加“@jjoller”。 更新:实际上我比 Spille 和 whitebrows 更喜欢这种方法,因为如果您有多个活动配置文件,使用 $spring.profiles.active 变量会失败。另一方面,这种@Profile 方法可以实现非常灵活的逻辑(记录在此:docs.spring.io/spring-framework/docs/current/javadoc-api/org/…)【参考方案5】:你可以这样做:
<context:property-placeholder location="classpath:$spring.profiles.active.properties" />
编辑:如果您需要更高级的东西,您可以在应用程序启动时注册您的 PropertySources。
web.xml
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.xxx.core.spring.properties.PropertySourcesApplicationContextInitializer</param-value>
</context-param>
您创建的文件:
public class PropertySourcesApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>
private static final Logger LOGGER = LoggerFactory.getLogger(PropertySourcesApplicationContextInitializer.class);
@Override
public void initialize(ConfigurableApplicationContext applicationContext)
LOGGER.info("Adding some additional property sources");
String[] profiles = applicationContext.getEnvironment().getActiveProfiles()
// ... Add property sources according to selected spring profile
// (note there already are some property sources registered, system properties etc)
applicationContext.getEnvironment().getPropertySources().addLast(myPropertySource);
完成后,您只需在上下文中添加:
<context:property-placeholder/>
我无法真正回答您关于多个配置文件的问题,但我猜您在这样的初始化程序上激活它们,并且您可以在配置文件激活期间注册适当的 PropertySource 项。
【讨论】:
您可以使用applicationContext.getEnvironment().getActiveProfiles()
以数组形式访问已解析的活动配置文件列表
没错,两者都可以。那时我不知道我们可以传递多个配置文件;)【参考方案6】:
注意:此答案提供了使用 @PropertySource
的属性文件的替代解决方案。我走这条路是因为尝试使用多个属性文件太麻烦了,这些属性文件可能每个都有覆盖,同时避免重复代码。
为每个相关的属性集创建一个 POJO 接口以定义它们的名称和类型。
public interface DataSourceProperties
String driverClassName();
String url();
String user();
String password();
实现返回默认值。
public class DefaultDataSourceProperties implements DataSourceProperties
public String driverClassName() return "com.mysql.jdbc.Driver";
...
每个配置文件(例如开发、生产)的子类,并覆盖与默认值不同的任何值。这需要一组互斥的配置文件,但您可以轻松添加“默认”作为“覆盖”的替代方案。
@Profile("production")
@Configuration
public class ProductionDataSourceProperties extends DefaultDataSourceProperties
// nothing to override as defaults are for production
@Profile("development")
@Configuration
public class DevelopmentDataSourceProperties extends DefaultDataSourceProperties
public String user() return "dev";
public String password() return "dev";
最后,将属性配置自动装配到需要它们的其他配置中。这里的好处是你不会重复任何@Bean
创建代码。
@Configuration
public class DataSourceConfig
@Autowired
private DataSourceProperties properties;
@Bean
public DataSource dataSource()
BoneCPDataSource source = new BoneCPDataSource();
source.setJdbcUrl(properties.url());
...
return source;
我仍然不相信我会坚持这一点,而不是根据 servlet 上下文初始化程序中的活动配置文件手动配置属性文件。我的想法是进行手动配置不适合单元测试,但我现在不太确定。我真的更喜欢阅读属性文件而不是属性访问器列表。
【讨论】:
属性和@PropertySource
的主要特点是您可以通过各种方式覆盖它——例如使用环境变量或应用程序开关【参考方案7】:
除了您建议的 Emerson 之外,我想不出任何其他方法,即在带有 @Profile
注释的单独 @Configuration
文件中定义此 bean:
@Configuration
@Profile("override")
@PropertySource("classpath:override.properties")
public class OverriddenConfig
@Autowired
private Environment environment;
@Bean
public Bean bean()
//if..
【讨论】:
以上是关于Spring配置文件可以选择@PropertySources吗?的主要内容,如果未能解决你的问题,请参考以下文章
携程Apollo client Spring整合启动过程源码追踪
Spring配置文件可以选择@PropertySources吗?
Spring Boot 库项目未从 /conf 文件夹中选择外部文件配置