【问题标题】:Can @PropertySources be chosen by Spring profile?Spring配置文件可以选择@PropertySources吗?
【发布时间】: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 支持。

【问题讨论】:

    标签: spring


    【解决方案1】:

    在静态内部类中添加覆盖 @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/…
    【解决方案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】:

    你可以这样做:

      <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()以数组形式访问已解析的活动配置文件列表
    • 没错,两者都可以。那时我不知道我们可以传递多个配置文件;)
    【解决方案4】:

    如果您需要支持多个配置文件,您可以执行以下操作:

    @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 设置
    【解决方案5】:

    除了您建议的 Emerson 之外,我想不出任何其他方法,即在带有 @Profile 注释的单独 @Configuration 文件中定义此 bean:

    @Configuration
    @Profile("override")
    @PropertySource("classpath:override.properties")
    public class OverriddenConfig {
    
        @Autowired
        private Environment environment;
    
        @Bean
        public Bean bean() {
            //if..
        }
    }
    

    【讨论】:

      【解决方案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】:

      这里提到的所有解决方案都有些尴尬,只适用于一个配置文件预设,它们不适用于更多/其他配置文件。目前有一个 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 {
      }
      

      【讨论】:

        猜你喜欢
        • 2014-02-16
        • 1970-01-01
        • 2017-06-13
        • 2015-11-05
        • 2017-04-12
        • 2015-06-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多