Введение

Ситуация

Большинство наших приложений зависят от внешних сервисов, например серверов баз данных, SMS-шлюзов и систем наподобие PayPal. Эти сервисы могут существовать более чем в одной среде, то есть в средах разработки и эксплуатации. Если мы хотим подключиться к эксплуатационной среде, мы должны сначала пройти через среду разработки. Таким образом, во время создания приложений нам приходится переключаться между средами. Это связано с тем, что у каждой среды своя уникальная конфигурация со своими параметрами подключения и прочими значениями.

Проблема

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

Решение

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

Вывод данных конфигурации во внешний источник

Источники свойств

Существуют различные способы вывода данных конфигурации приложения Spring во внешний источник. Для задания свойств приложения мы можем использовать переменные среды, файлы свойств (например, в формате YAML или с расширением *.properties) и аргументы командной строки. Мы также можем хранить файлы свойств в произвольных местах и сообщать приложению Spring, где их искать.

Файлы свойств

По умолчанию приложение Spring загружает свойства из файлов application.properties или application.yml из перечисленных ниже источников в порядке приоритета (то есть вышестоящий файл свойств переопределяет файлы из источников нижнего уровня) и добавляет их в среду:

  1. подкаталог конфигурации текущего каталога;

  2. текущий каталог;

  3. пакет конфигураций в параметре classpath;

  4. корневой каталог classpath.

По умолчанию имя файла конфигурации — application. При желании мы можем указать другое имя, используя ключ свойств среды spring.config.name. В примере ниже мы переопределили имя конфигурации Spring, заданное по умолчанию, на new_name.

spring.config.name=new_name

Пользовательское место хранения

Мы можем задать внешний источник свойств приложения или файлов YAML с помощью свойства среды spring.config.location. Это свойство может указывать на любое пользовательское место хранения и таким образом переопределять местоположение по умолчанию. См. пример ниже:

spring.config.location={path_to_configuration_file/directory}

Примечание. При указании расположения каталога необходимо убедиться, что после значения spring.config.location стоит символ / (например, spring.config.location=classpath:/config/) и что задано имя файла конфигурации по умолчанию. Также с помощью ключа свойств spring.config.additional-location можно указать дополнительные каталоги, поиск в которых будет проводиться перед поиском в местоположениях по умолчанию.

spring.config.additional-location={path_to_configuration_file/directory}

Spring Boot также поддерживает обобщенное указание местоположения с помощью подстановочных символов. Эта функция полезна в средах с несколькими источниками свойств конфигурации, таких как среды Kubernetes. Например, у вас есть конфигурации Redis и MySQL. Они могут храниться в разных местах, но при этом они обе должны быть указаны в файле application.properties, чтобы их видело приложение. Это может привести к тому, что два отдельных файла application.properties будут смонтированы в разных местах, например /config/redis/application.properties и /config/mysql/application.properties. В таком случае использование обобщенного указания каталога config/*/ позволит обрабатывать оба файла.

Форматы файлов

Файл свойств приложения может быть в формате YAML или иметь расширение .properties. Если эти два файла свойств будут храниться в одной и той же папке конфигурации, файл application.properties будет иметь приоритет над файлом application.yml. В следующем фрагменте кода показаны настройки коммерческого счета, определенные в файле свойств каждого типа.

application.properties

merchantaccount.name=Maureen Sindiso Mpofu
merchantaccount.username=momoe
merchantaccount.code=771222279
merchantaccount.number=100
merchantaccount.currency=ZWL
server.port: 9092

application.yml

merchantaccount:
 name: Maureen Sindiso Mpofu
 username: momoe
 code: MCA1230
 number: 771222279
 currency: ZWL

Файлы форматов YAML и .properties

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

Для загрузки файла YAML приложению Spring требуется библиотека SnakeYAML  в параметре classpath. В приведенном примере кода использованы стартеры Spring Boot, поэтому необходимости включать данную библиотеку в параметр classpath нет.

Множество профилей

YAML позволяет указать несколько профилей в одном файле конфигурации, тогда как при использовании файла .property нам может потребоваться файл конфигурации для каждого профиля. Рассмотрим следующий пример.

1. Файл YAML

application.yml

spring:
 profiles:
   active: development
---
spring:
 profiles: development
merchantaccount:
 name: Maureen Sindiso Mpofu
 username: momoe
 code: MCA1230
 number: 771222279
 currency: ZWL
server:
 port: 9090
---
spring:
 profiles: production
server:
 port: 9093
merchantaccount:
 name: Maureen Sindiso Mpofu
 username: momoe
 code: MCA1234
 number: 771222279
 currency: ZWD

2. Файл .properties

В случае с файлом .properties, при определении двух профилей нужно создать отдельный файл конфигурации для каждого из них. К имени каждого файла конфигурации добавляется -{имя_профиля}.properties. В примере ниже показаны профили приложения для разработки и эксплуатации.

application-development.properties

merchantaccount.name=Maureen Sindiso Mpofu
merchantaccount.username=momoe
merchantaccount.code=771222279
merchantaccount.number=100
merchantaccount.currency=ZWL
server.port: 9092

application-production.properties

merchantaccount.name=Maureen Sindiso Mpofu
merchantaccount.username=momoe
merchantaccount.code=MCA1234
merchantaccount.number=771222279
merchantaccount.currency=ZWD
server.port: 9093

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

application.properties

spring.profiles.active=development
#default port number
server.port=9091

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

Вы можете узнать больше о профилях Spring в этой статье.

Читаемость

YAML поддерживает списки и карты в виде иерархических свойств, и по сравнению с файлом расширения .properties версия YAML более удобочитаемая. Допустим, мы хотим настроить параметры подключения для реальной и тестовой сред. Сначала зададим имена подключений в виде списка, а затем сопоставим их с соответствующими URL-адресами с помощью карты, как показано ниже. Рассмотрим, как реализация в YAML может упростить эту конфигурацию в сравнении с файлом .properties.

application.yml

connection:
 names:
   - test
   - live
 addresses:
   test: http://host/test
   live: http://host/live

application.properties

#list
connection.names[0]=test
connection.names[1]=live
#map
connection.addresses.test=http://host/test
connection.addresses.live= http://host/live

Тестовые примеры для проверки сопоставлений можно найти в тестовых пакетах с примером кода из данной статьи.

Аргументы командной строки

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

Команда Maven: 

$mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=production"

Команда JVM:

$java -jar target/app.jar – spring.profiles.active=production

Также можно вводить несколько аргументов одновременно. Дополним приведенный выше пример еще одним свойством — портом сервера, как показано ниже.

Команда Maven (через пробел): 

$mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=production – server.port=8089"

Команда JVM:

$java -jar target/app.jar – spring.profiles.active=production – server.port=8089

Переменные среды

Если у нас нет возможности изменять значения свойств через командную строку, на выручку приходят переменные среды. Приложение Spring может считывать свойства из них. При запуске оно ищет переменную среды под именем SPRING_APPLICATION_JSON, которая может содержать набор свойств JSON в одностроковом формате. Мы можем поэкспериментировать и переопределить адреса подключения, указанные в нашем файле свойств, как описано ниже.

Откроем терминал и выполним следующую команду. Она устанавливает переменные среды приложения, переопределяя настройки подключения.

$export SPRING_APPLICATION_JSON='{"connection":{"addresses":{"test":"http://localhost/payments/pre-prod1","live":"http://192.168.123.23/payments/prod1"}}}'

После этого запустим наше приложение:

$java -jar -Dspring.profiles.active=development target/app.jar

Результат

Проверив журнал, мы заметим, что адреса подключения в профиле разработки были переопределены, а значения в файле JSON, который мы передали через переменную среды, были в приоритете.

Передача свойств

Существуют различные способы передачи значений свойств в приложение из соответствующих источников. Мы можем использовать аннотацию @Value абстракции Spring Environment или привязать эти значения к структурированному объекту с аннотацией @ConfigurationProperties.

@Value

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

@Value(“${propertyName}”)

Важно убедиться, что имя свойства @Value совпадает с именем, указанным в источниках свойств.

@ConfigurationProperties

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

@Data
@Component
@ConfigurationProperties(prefix = "merchantaccount")
public class MerchantAccount {
  private String name;
  private String username;
  private String code;
  private int number;
  private String currency;
}

Класс POJO должен иметь аннотации @ConfigurationProperties и @Component, как описано выше. Значение префикса, указанное в аннотации, должно совпадать с префиксом свойства, определенного в файле application.yml.

application.yml

merchantaccount:
 name: Maureen Sindiso Mpofu
 username: momoe
 code: MCA1230
 number: 771222279
 currency: ZWL

Важно отметить, что аннотация @ConfigurationProperties также позволяет нам сопоставлять списки и карты, как показано ниже:

@ConfigurationProperties(prefix = "connection")
@Component
@Data
public class ConnectionSettings {
   List<String> names;
   Map<String, String> addresses;
}

Порядок приоритета данных конфигурации

В приложении Spring Boot может быть несколько источников свойств. Поэтому важно знать, какой источник свойства имеет наивысший приоритет. Например, если конфигурация нашего приложения находится в файле application.yml и во время выполнения приложения мы решаем передать аргументы командной строки, тогда значения свойств в файле application.yml будут переопределены значениями аргументов командной строки.

В Spring Boot 2.2.x используется приведенный ниже порядок источников свойств.  Источник свойств, расположенный выше в списке, имеет приоритет над источниками под ним.

  1. Свойства глобальных настроек в папке $HOME/.config/spring-boot, когда средства разработки активны.

  2. Аннотации @TestPropertySource в ваших тестах.

  3. Атрибут свойств в ваших тестах. Он доступен в @SpringBootTest и тестовых аннотациях для проверки работы определенного фрагмента вашего приложения.

  4. Аргументы командной строки.

  5. Свойства из SPRING_APPLICATION_JSON (строковый JSON в переменной среды или системном свойстве).

  6. Начальные параметры ServletConfig.

  7. Начальные параметры ServletContext.

  8. Атрибуты JNDI из java:comp/env.

  9. Свойства Java System, то есть System.getProperties().

  10. Переменные среды ОС.

  11. RandomValuePropertySource, свойства которого хранятся только в random.*.

  12. Свойства приложения для конкретного профиля, за пределами упакованного файла .jar (application- {profile}.properties и варианты YAML).

  13. Свойства приложения для конкретного профиля внутри файла .jar (application- {profile}.properties и варианты YAML).

  14. Свойства приложения за пределами упакованного файла .jar (application.properties и варианты YAML).

  15. Свойства приложения в файле .jar (application.properties и варианты YAML).

  16. Аннотации @PropertySource в классах @Configuration. Необходимо учесть, что такие источники свойств не добавляются в Environment, пока контекст приложения не будет обновлен. В этот момент уже поздно настраивать некоторые свойства, например logging.* и spring.main.*, которые считываются перед началом обновления.

  17. Свойства по умолчанию (заданные настройкой SpringApplication.setDefaultProperties). 

Заключение

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


Материал подготовлен в рамках специализации «Java Developer».

Комментарии (2)


  1. remirran
    08.09.2021 20:26

    Сам когда-то искал, оставлю тут, вдруг кому пригодится. В случае если приложение крутится в томкате можно задать путь через конфиг: <tomcat path>/conf/Catalina/localhost/<application>.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <Context>
            <Environment name="spring.config.location" value="file:/<path to>/application.properties" type="java.lang.String"/>
    </Context>


  1. Anshelen
    22.09.2021 08:49

    Класс POJO должен иметь аннотации @ConfigurationProperties и @Component, как описано выше

    Аннотация @Component не обязательна, т.к. спринг и без нее создаст нужный бин