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 配置中有效,但当我使用 EnvironmentgetRequiredProperty() 方法时则无效。

<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.propertiesconfig-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基于范围的默认配置文件选择

Spring Boot 库项目未从 /conf 文件夹中选择外部文件配置

Jenkins选择Spring Boot配置文件的任何方式?

环境配置——IDEA搭建maven+spring mvc开发环境