«1. Обзор

Начиная с Spring Boot 1.3, мы можем использовать EnvironmentPostProcessor для настройки среды приложения до обновления контекста приложения.

В этом руководстве мы рассмотрим, как загрузить и преобразовать пользовательские свойства в среду, а затем получить доступ к этим свойствам.

2. Spring Environment

Абстракция Environment в Spring представляет среду, в которой выполняется текущее приложение. В то же время он имеет тенденцию к унификации способов доступа к свойствам в различных источниках свойств, таких как файлы свойств, системные свойства JVM, системные переменные среды и параметры контекста сервлета.

Таким образом, в большинстве случаев настройка среды означает манипулирование различными свойствами до того, как они станут доступными для наших компонентов. Для начала посетите нашу предыдущую статью об управлении свойствами с помощью Spring.

3. Краткий пример

Давайте теперь создадим простое приложение для расчета цен. Он рассчитает цену либо в валовом, либо в нетто-режиме. Переменные системной среды от третьей стороны будут определять, какой режим расчета выбрать.

3.1. Реализация EnvironmentPostProcessor

Для этого давайте реализуем интерфейс EnvironmentPostProcessor.

Мы будем использовать его для чтения нескольких переменных окружения:

calculation_mode=GROSS 
gross_calculation_tax_rate=0.15

И мы будем использовать постпроцессор, чтобы предоставить их в зависимости от приложения, в данном случае с пользовательским префиксом: ~ ~~

com.baeldung.environmentpostprocessor.calculation.mode=GROSS
com.baeldung.environmentpostprocessor.gross.calculation.tax.rate=0.15

Затем мы можем просто добавить наши новые свойства в среду:

@Order(Ordered.LOWEST_PRECEDENCE)
public class PriceCalculationEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, 
      SpringApplication application) {
        PropertySource<?> system = environment.getPropertySources()
          .get(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
        if (!hasOurPriceProperties(system)) {
          // error handling code omitted
        }
        Map<String, Object> prefixed = names.stream()
          .collect(Collectors.toMap(this::rename, system::getProperty));
        environment.getPropertySources()
          .addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new MapPropertySource("prefixer", prefixed));
    }

}

Давайте посмотрим, что мы здесь сделали. Во-первых, мы попросили среду предоставить нам PropertySource для переменных среды. Вызов результирующего system.getProperty подобен вызову Java System.getenv().get.

Затем, пока эти свойства существуют в среде, мы создадим новую карту с префиксом. Для краткости мы пропустим содержимое rename, но ознакомьтесь с примером кода для полной реализации. Результирующая карта имеет те же значения, что и система, но с префиксными ключами.

Наконец, мы добавим наш новый PropertySource в среду. Теперь, если бин запрашивает com.baeldung.environmentpostprocessor.calculation.mode, Environment сверится с нашей картой.

Обратите внимание, кстати, что Javadoc EnvironmentPostProcessor рекомендует нам либо реализовать интерфейс Ordered, либо использовать аннотацию @Order.

И это, конечно, только один источник свойств. Spring Boot позволяет нам работать с многочисленными источниками и форматами.

3.2. Регистрация в spring.factories

Чтобы вызвать реализацию в процессе начальной загрузки Spring Boot, нам нужно зарегистрировать класс в META-INF/spring.factories:

org.springframework.boot.env.EnvironmentPostProcessor=
  com.baeldung.environmentpostprocessor.PriceCalculationEnvironmentPostProcessor

3.3. Доступ к свойствам с помощью аннотации @Value

Давайте используем их в паре классов. В примере у нас есть интерфейс PriceCalculator с двумя реализациями: GrossPriceCalculator и NetPriceCalculator.

В наших реализациях мы можем просто использовать @Value для получения наших новых свойств:

public class GrossPriceCalculator implements PriceCalculator {
    @Value("${com.baeldung.environmentpostprocessor.gross.calculation.tax.rate}")
    double taxRate;

    @Override
    public double calculate(double singlePrice, int quantity) {
        //calcuation implementation omitted
    }
}

Это хорошо, так как мы получаем доступ к любым другим свойствам, таким как те, которые мы определили в application.properties.

3.4. Доступ к свойствам в автонастройке Spring Boot

Теперь давайте рассмотрим сложный случай, когда мы обращаемся к предыдущим свойствам в автонастройке Spring Boot.

Мы создадим класс автоконфигурации для чтения этих свойств. Этот класс будет инициализировать и связывать bean-компоненты в контексте приложения в соответствии с различными значениями свойств:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PriceCalculationAutoConfig {
    @Bean
    @ConditionalOnProperty(name = 
      "com.baeldung.environmentpostprocessor.calculation.mode", havingValue = "NET")
    @ConditionalOnMissingBean
    public PriceCalculator getNetPriceCalculator() {
        return new NetPriceCalculator();
    }

    @Bean
    @ConditionalOnProperty(name = 
      "com.baeldung.environmentpostprocessor.calculation.mode", havingValue = "GROSS")
    @ConditionalOnMissingBean
    public PriceCalculator getGrossPriceCalculator() {
        return new GrossPriceCalculator();
    }
}

Подобно реализации EnvironmentPostProcessor, класс автоконфигурации также необходимо зарегистрировать в META-INF/spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  com.baeldung.environmentpostprocessor.autoconfig.PriceCalculationAutoConfig

Это работает, потому что пользовательские реализации EnvironmentPostProcessor срабатывают раньше, чем автоконфигурация Spring Boot. Эта комбинация делает автоконфигурацию Spring Boot более мощной.

И, чтобы узнать больше об автонастройке Spring Boot, ознакомьтесь со статьей Custom Auto-Configuration with Spring Boot.

4. Протестируйте пользовательскую реализацию

«Теперь пришло время протестировать наш код. Мы можем установить переменные системной среды в Windows, запустив:

set calculation_mode=GROSS
set gross_calculation_tax_rate=0.15

Или в Linux/Unix мы можем вместо этого экспортировать их:

export calculation_mode=GROSS 
export gross_calculation_tax_rate=0.15

После этого мы можем запустить тест с mvn spring-boot :run команда:

mvn spring-boot:run
  -Dstart-class=com.baeldung.environmentpostprocessor.PriceCalculationApplication
  -Dspring-boot.run.arguments="100,4"

5. Заключение

Подводя итог, можно сказать, что реализация EnvironmentPostProcessor способна загружать произвольные файлы в различных форматах из разных мест. Кроме того, мы можем выполнить любое преобразование, необходимое для того, чтобы сделать свойства доступными в среде для последующего использования. Эта свобода, безусловно, полезна, когда мы интегрируем приложение на основе Spring Boot со сторонними конфигурациями.

Исходный код можно найти в репозитории GitHub.