Может ли @PropertySources выбираться профилем Spring?

У меня есть Spring 3.1 @Configuration, которому требуется свойство foo для построения bean. Свойство определено в defaults.properties, но может быть переопределено свойством в overrides.properties, если приложение имеет активный профиль override Spring.

Без переопределения код будет выглядеть так и работать...

@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().

Размещение следующего в конфигурации XML работает, если я вставляю свойство непосредственно с помощью @Value, но не тогда, когда я использую метод 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, в частности "Использование YAML вместо свойств", раздел. Поддержка профиля там сделает этот вопрос спорным, но пока не поддерживается @PropertySource.

Ответ 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");
        ...
    }
}

Ответ 2

Вы можете сделать:

  <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 во время активации профиля.

Ответ 3

Я не могу думать иначе, чем тот, который вы предложили Emerson, который должен определить этот bean в отдельном файле @Configuration с аннотацией @Profile:

@Configuration
@Profile("override")
@PropertySource("classpath:override.properties")
public class OverriddenConfig {

    @Autowired
    private Environment environment;

    @Bean
    public Bean bean() {
        //if..
    }
}

Ответ 4

Примечание.. Этот ответ предоставляет альтернативное решение для использования файлов свойств с @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"; }
     ...
}

Подкласс для каждого профиля (например, разработка, производство) и переопределение любых значений, которые отличаются от значений по умолчанию. Для этого требуется набор взаимоисключающих профилей, но вы можете легко добавить "default" в качестве альтернативы "переопределениям".

@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;
    }
}

Я все еще не убежден, что я буду придерживаться этого, чтобы вручную настроить файлы свойств на основе активных профилей в инициализаторе контекста сервлета. Моя мысль заключалась в том, что выполнение ручной настройки не было бы таким же поддающимся модульной проверке, но теперь я не уверен. Я действительно предпочитаю читать свойства свойств в списке аксессуаров свойств.

Ответ 5

Если вам нужно поддерживать несколько профилей, вы можете сделать что-то вроде этого:

@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 {
    }
}

Таким образом, вам не нужно определять статический класс конфигурации для каждого профиля. Спасибо, Дэвид Харкнесс, за то, что направил меня в правильном направлении.