Статьи

Проекты Spring Boot & Multi module — Добавление файлов свойств, специфичных для модуля

Здравствуйте!

В этом посте я покажу вам несколько способов, как вы можете добавить специфичные для модуля файлы свойств в проект Spring Boot. В нем будет описан ручной подход к созданию профиля файлов свойств и полуавтоматический способ определения профиля. Пример проекта опубликован на моей учетной записи Github ( https://github.com/coders-kitchen/spring-boot-multi-module-property-files ).

Причины наличия выделенных файлов свойств для модуля в многомодульном проекте разнообразны. Во-первых, вы хотели бы легко вырезать модуль в собственный сервис. Наличие собственных файлов свойств для каждого модуля поможет в этом, поскольку это ясно дает понять пользователю, что он просто должен извлечь все файлы из модуля, чтобы он был автономным. Или что вы хотите указать значения по умолчанию для каждого модуля, которые могут быть перезаписаны основными свойствами.

В общем есть три варианта

  • Указание дополнительных файлов свойств через активные профили
  • Настройка конфигурации в модулях, использующих аннотацию @PropertySource
  • Информирование весеннего контекста о дополнительных шаблонах файлов

Давайте обсудим одно за другим:

Файлы свойств, указанные в активных профилях

Этот подход использует механизм активных профилей Spring для активации дополнительных файлов свойств. Например, активный профиль local будет считывать также настройки из файла application-local.properties .

Преимущество этого подхода состоит в том, что вы просто используете стандартный механизм добавления новых файлов свойств для каждого модуля. И они могут быть указаны в основных файлах application.properties или специализированных application- <profile> .properties .

Недостатки в том, что вы должны помнить каждый раз, когда добавляете активные профили в правильном порядке, например, профиль module1 должен находиться непосредственно перед module1-production, чтобы последний перезаписывал профиль по умолчанию.

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

Руководство через @PropertySource

Сам Spring поставляет аннотацию для добавления дополнительных файлов свойств в контекст. Он называется @PropertySource и может использоваться на уровне класса (см. Следующий пример).

1
2
3
4
5
@Configuration
@PropertySource("classpath:application-module1.properties")
public class MyPropertyConfig {
  
}

Чтобы этот подход учитывал несколько профилей, вы можете использовать его таким образом

01
02
03
04
05
06
07
08
09
10
11
12
13
@Configuration
public class MyPropertyConfig {
  
  @Configuration
  @PropertySource("classpath:application-module1.properties")
  @Profile("default")
  static class DefaultConfig {}
  
  @Configuration
  @PropertySource("classpath:application-module1-production.properties")
  @Profile("production")
  static class ProductionConfig {}
}

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

Недостатки в том, что этот подход работает очень хорошо только для предопределенного набора профилей, но когда вы хотите добавить новый профиль, вы должны помнить, что вы должны добавить его также в класс MyPropertyConfig . Кроме того, когда вы изменяете имя модуля, вы должны изменять не только имена файлов, но и ссылки в коде.

Добавление нового шаблона файла свойств к источникам свойств

Этот способ является наиболее общим, так как он напрямую вводит новые шаблоны файлов свойств в контекст и автоматически распознает профиль. Чтобы заставить его работать, вы должны использовать механику расширения через интерфейс ApplicationListener .

Это позволяет напрямую прослушивать событие ApplicationEnvironmentPreparedEvent которое запускается после подготовки среды выполнения, но до ее загрузки. Это позволяет вам добавлять файлы к источникам свойств, например. Событие предоставляет доступ к ConfigurableEnvironment, которая предоставляет, помимо прочего, информацию об активных профилях.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class PropertyFilePatternRegisteringListener implements ApplicationListener {
  
  public static final String PROPERTY_FILE_PREFIX = &quot;application-module3&quot;;
  private static final String FILE_SUFFIX = &quot;.properties&quot;;
  
  @Override
  public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    try {
      loadProfileProperties(environment);
      loadPlainProperties(environment);
    } catch (IOException ex) {
      throw new IllegalStateException(&quot;Unable to load configuration files&quot;, ex);
    }
  }
  
  private void loadProfileProperties(ConfigurableEnvironment environment) throws IOException {
    String[] activeProfiles = environment.getActiveProfiles();
    if(activeProfiles != null && activeProfiles.length > 0)
      loadProfileProperties(environment, activeProfiles);
    else
      loadProfileProperties(environment, environment.getDefaultProfiles());
  }
  
  private void loadProfileProperties(ConfigurableEnvironment environment, String[] profiles) throws IOException {
    for (String activeProfile : profiles) {
      addFileToEnvironment(environment, PROPERTY_FILE_PREFIX + &quot;-&quot; + activeProfile + FILE_SUFFIX);
    }
  }
  
  private void loadPlainProperties(ConfigurableEnvironment environment) throws IOException {
    addFileToEnvironment(environment, PROPERTY_FILE_PREFIX + FILE_SUFFIX);
  }
  
  private void addFileToEnvironment(ConfigurableEnvironment environment, String file) throws IOException {
    ClassPathResource classPathResource = new ClassPathResource(file);
    if (classPathResource.exists()) {
      environment.getPropertySources()
                 .addLast(new ResourcePropertySource(classPathResource));
    }
  }
}

Чтобы активировать его, вы должны добавить его в качестве ApplicationListener при загрузке контекста приложения, как это

1
2
3
4
5
6
new SpringApplicationBuilder()
        .listeners(new PropertyFilePatternRegisteringListener())
        .main(Application.class)
        .registerShutdownHook(true)
        .run(args);
  }

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

Недостатки в том, что вы должны добавить слушателя в основной модуль для каждого подмодуля. И использование дополнительных / различных файлов свойств (или, по крайней мере, варианта по умолчанию) в тестах не является простым. На момент написания этой статьи я знал только об использовании @PropertySource в интеграционных тестах для выполнения этой работы. Также ознакомление со всеми случаями, которые поддерживает загрузчик Spring по умолчанию, является более сложным, чем подход, описанный выше.

Резюме

В этом посте мы обсудили несколько способов добавления новых файлов свойств в приложение Spring Boot. Все варианты имеют свои преимущества и недостатки.

В зависимости от варианта использования я бы выбрал либо @PropertySource, либо использование интерфейса ApplicationListener . Первый достаточно хорош, если вы просто хотите иметь общий набор свойств, которые могут быть перезаписаны для определенных профилей или для свойств основных модулей. Последний является наиболее общим подходом и должен использоваться, когда вам абсолютно необходима эта гибкость.