Spring Boot предоставляет широкие возможности для внешней конфигурации приложения. Благодаря этому один и тот же артефакт приложения можно запускать в разных средах, передавая значения из различных источников, таких как:
файлы свойств;
переменные окружения;
системные свойства;
аргументы командной строки.
В этой статье мы рассмотрим лучшие практики управления конфигурацией приложений Spring Boot.
Хорошо продуманная стратегия конфигурации должна обеспечивать следующее:
конфигурация остается отделенной от кода приложения;
приложение не запускается, если обязательная конфигурация отсутствует или некорректна;
значения по умолчанию можно переопределять для каждой среды развертывания;
конфиденциальные значения предоставляются специализированной системой управления секретами.
Классификация параметров конфигурации
Как правило, конфигурацию приложения Spring Boot можно разделить на три категории:
Значения приложения по умолчанию: безопасные, несекретные значения, например тайм-ауты к сторонним сервисам и ограничения на количество повторных попыток. Храните их вместе с приложением.
Конфигурация развертывания: значения, определяющие конкретную среду, например адреса серверов баз данных, имена очередей и URL внешних сервисов. Передавайте их через платформу развертывания.
Секреты: пароли, API-ключи, сертификаты и закрытые ключи. Храните их в специализированной системе управления секретами.
Например, application.properties может содержать значения конфигурации приложения по умолчанию:
app.promotion-service.base-url=http://localhost:8181 app.promotion-service.timeout=3s app.promotion-service.retries=3 logging.level.com.jetbrains=DEBUG spring.jpa.hibernate.ddl-auto=validate spring.jpa.open-in-view=false
Значение по умолчанию должно быть безопасным для любой среды, в которой оно может использоваться. Такие параметры, как URL базы данных и учетные данные, никогда не следует жестко прописывать в коде приложения. Если для обязательного параметра нет безопасного значения по умолчанию, проверяйте его наличие при запуске.
Используйте @ConfigurationProperties для привязки свойств приложения
Приложения Spring могут получать доступ к значениям конфигурации через Environment, @Value или @ConfigurationProperties.
Используйте Environment, когда имена свойств необходимо определять динамически или инфраструктурному коду нужен прямой доступ к источникам свойств.
Используйте @Value для отдельных значений:
PromotionService( @Value("${app.promotion-service.base-url}") String baseUrl, @Value("${app.promotion-service.timeout}") Duration timeout, @Value("${app.promotion-service.retries}") int retries) { this.baseUrl = baseUrl; this.timeout = timeout; this.retries = retries; }
Разрозненные выражения @Value затрудняют поиск имен свойств, их валидацию и рефакторинг. Отдельный тип конфигурации на основе @ConfigurationProperties поддерживает все эти возможности.
Для связанных параметров конфигурации предпочтительнее использовать @ConfigurationProperties. Эта аннотация обеспечивает:
типобезопасную привязку и преобразование значений;
гибкую привязку между именами свойств и членами Java-классов;
валидацию на уровне группы;
автодополнение и навигацию в IDE через сгенерированные метаданные.
Например, при интеграции со сторонним REST API нам может понадобиться настроить базовый URL сервиса, тайм-аут и количество повторных попыток.
app.promotion-service.base-url=${PROMOTION_SERVICE_URL} app.promotion-service.timeout=${PROMOTION_SERVICE_TIMEOUT:3s} app.promotion-service.retries=3
В этой конфигурации значение base-url задается из переменной окружения PROMOTION_SERVICE_URL, а значение timeout из переменной окружения PROMOTION_SERVICE_TIMEOUT со значением по умолчанию 3 секунды.
Spring Boot поддерживает привязку через сеттеры. Свойства можно привязать к классу, использующему сеттеры, следующим образом:
@ConfigurationProperties(prefix = "app.promotion-service") public class PromotionSvcProperties { private String baseUrl; private Duration timeout; private int retries; // Setters and getters }
Зарегистрируйте типы конфигурации с помощью @ConfigurationPropertiesScan:
@SpringBootApplication @ConfigurationPropertiesScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Аннотация @ConfigurationPropertiesScan ищет компоненты, помеченные @ConfigurationProperties, и регистрирует их как Spring-бины.
Теперь PromotionSvcProperties можно внедрять в другие Spring-бины и получать через него значения свойств.
Для привязки @ConfigurationProperties отдавайте предпочтение record
Как правило, конфигурация задается во время запуска и остается неизменной на протяжении всего жизненного цикла приложения.
Для большинства конфигураций приложения предпочтительным вариантом является Java record. Он изначально обеспечивает неизменяемость, поэтому значения нельзя случайно изменить, в отличие от привязки через обычный класс, где можно непреднамеренно вызвать сеттер:
@ConfigurationProperties(prefix = "app.promotion-service") public record PromotionSvcProperties( String baseUrl, Duration timeout, int retries) { }
Механизм гибкой привязки Spring Boot сопоставляет канонические имена в kebab-case, например base-url, с полем baseUrl.
Иногда требуется привязать свойства к бину, предоставляемому сторонней библиотекой, исходный код которой нельзя изменить, чтобы добавить аннотацию @ConfigurationProperties.
Чтобы напрямую привязать параметры конфигурации к стороннему классу, объявите его как @Bean и пометьте метод бина аннотацией @ConfigurationProperties:
@Configuration public class ClientConfiguration { @Bean @ConfigurationProperties(prefix = "third-party.client") public ThirdPartyClientProperties clientProperties() { return new ThirdPartyClientProperties(); } }
Свойства third-party.client можно настроить следующим образом:
third-party.client.base-url=https://api.example.com third-party.client.connect-timeout=5s third-party.client.read-timeout=30s
Если сторонний класс неизменяемый или не поддерживает привязку через сеттеры, создайте собственный класс свойств и используйте его для создания стороннего объекта:
@ConfigurationProperties(prefix = "third-party.client") public record ClientProperties( URI baseUrl, Duration connectTimeout, Duration readTimeout ) {} @Configuration @EnableConfigurationProperties(ClientProperties.class) class ClientConfiguration { @Bean ThirdPartyClient thirdPartyClient(ClientProperties properties) { return new ThirdPartyClient( properties.baseUrl(), properties.connectTimeout(), properties.readTimeout() ); } }
Подход с оберткой, как правило, предпочтительнее, поскольку он позволяет избежать прямой зависимости конфигурации приложения от структуры классов сторонней библиотеки.
Ошибки нужно выявлять как можно раньше: проверяйте конфигурацию при запуске
Ошибки конфигурации следует выявлять во время запуска приложения и немедленно останавливать запуск, если обязательная конфигурация отсутствует или содержит некорректные значения. Добавьте @Validated к бину @ConfigurationProperties и примените к его свойствам ограничения Jakarta Bean Validation.
@Validated @ConfigurationProperties(prefix = "app.promotion-service") public record PromotionSvcProperties( @NotBlank String baseUrl, @NotNull Duration timeout, @Min(1) @Max(5) int retries, @NotNull @Valid SyncProperties sync) { public record SyncProperties(@NotEmpty String cron) { } }
Если spring-boot-starter-validation присутствует в classpath, ошибки привязки или валидации остановят запуск приложения. Проверяйте обязательные значения, числовые диапазоны, вложенные группы и другие ограничения на уровне приложения.
Используйте классы-обертки, если необходимо отличать отсутствие значения от значения Java по умолчанию. Например, Integer с аннотацией @NotNull позволяет определить, что значение отсутствует, тогда как int по умолчанию получает значение 0.
Значения по умолчанию также можно задавать с помощью @DefaultValue:
@Validated @ConfigurationProperties(prefix = "app.promotion-service") public record PromotionSvcProperties( @NotBlank String baseUrl, @NotNull Duration timeout, @Min(1) @Max(5) @DefaultValue("3") Integer retries, @NotNull @Valid SyncProperties sync) { public record SyncProperties(@DefaultValue("0 0 * * * *") String cron) { } }
В этом примере с помощью @DefaultValue заданы значения по умолчанию для свойств retries и cron. Они будут использоваться, если соответствующие свойства не настроены.
Учитывайте приоритет источников свойств
Spring Boot объединяет несколько источников свойств. Если одно и то же свойство задано более чем в одном источнике, итоговое значение берется из источника с более высоким приоритетом.
Ниже приведен упрощенный порядок наиболее часто используемых при развертывании приложения источников, от самого низкого к самому высокому приоритету:
application.properties/yaml (низкий приоритет) ↓ файлы конфигурации для конкретных профилей ↓ переменные окружения ОС ↓ системные свойства Java ↓ аргументы командной строки (высокий приоритет)
Порядок загрузки конфигурации в Spring Boot важно учитывать при поиске причины, по которой фактическое значение отличается от ожидаемого.
Переменные окружения широко поддерживаются операционными системами, контейнерными средами выполнения и облачными платформами. Spring Boot формирует имена переменных окружения из канонических имен свойств: заменяет точки символами подчеркивания, удаляет дефисы и преобразует результат в верхний регистр:
app.payment-timeout -> APP_PAYMENT_TIMEOUT spring.datasource.url -> SPRING_DATASOURCE_URL
Комментарий от Михаила Поливаха
Данная практика называется Relaxed Binding.
Строго говоря, Spring Boot нормализует имена property в Environment не на переменные окружения, а на определенный внутренний формат. Такая нормализация происходит не только для переменных окружения, но и для свойств, пришедших из других PropertySource-ов, например из System Property и т.д.
Определить итоговое значение свойства бывает непросто, если оно задано сразу в нескольких источниках конфигурации. IntelliJ IDEA может отображать фактические значения конфигурации в виде встроенных подсказок прямо в редакторе. При выборе такой подсказки IDE показывает источник, из которого получено значение, а также указывает, было ли оно переопределено другим источником, например переменной окружения или системным свойством.

IDE также обеспечивает навигацию между объявлениями свойств, членами @ConfigurationProperties и местами использования свойств. Для пользовательских конфигурационных свойств эта поддержка дополнительно расширяется за счет метаданных, генерируемых spring-boot-configuration-processor.

Храните секреты в специализированной системе
Не храните пароли, API-ключи, сертификаты или закрытые ключи в системе контроля версий. Используйте такие решения, как HashiCorp Vault, AWS Secrets Manager, Google Cloud Secret Manager, Azure Key Vault или аналогичный сервис вашей платформы.
Убедитесь, что секреты не попадают в логи, сообщения об ошибках, метаданные конфигурации и общедоступные management endpoints.
ПРИМЕЧАНИЕ: В непроизводственных средах endpoint env модуля Actuator может помочь определить источник фактического значения свойства. Его не следует делать общедоступным, поскольку конфигурация может содержать конфиденциальные данные.
Комментарий от Михаила Поливаха
В теории, сокрыть sensative значения на деле в Spring Boot можно, но там прямо целая большая история с этим.
Было время, когда Spring Boot пытался понять самостоятельно, что прятать, а что нет. Было время, когда это можно было как-то по ключам настроить. Потом подход стал больше полагаться на SantizationFunction и т.д.
В общем случае, на данный момент, для современных Spring Boot приложений (Boot 3 и выше), если вы используете ендпоинты Actuator в том или ином виде, то вам придется маскировать либо все, либо никакие property в рамках Environment.
Это можно в теории настроить самим, написав собственную функцию санитизации. Тем не менее, на практике это довольно не просто, т.к. на практике это включает в себя в т.ч. какой-либо RBAC в лучшем случае, а в худшем ещё и ABAC.
Axelix, кстати из коробки дает возможность просто с помощью property сказать, какие свойства не должны быть видны никому, кроме администраторов в системе:
Рекомендуемые подходы к управлению конфигурацией
Не существует единого подхода к управлению конфигурацией, который подходил бы для любого приложения. Выбирайте стратегию с учетом архитектуры приложения, среды развертывания и уровня сложности.
Монолит
Для монолитного приложения храните общие значения по умолчанию в самом приложении, используйте файлы для отдельных профилей только там, где это действительно необходимо, а переопределения, зависящие от среды развертывания, передавайте через переменные окружения. Конфиденциальные значения храните в специализированном менеджере секретов.
Контейнерные приложения
Для приложений, работающих на контейнерной платформе, например Kubernetes, храните разумные значения по умолчанию в приложении, а конфигурацию, зависящую от конкретного развертывания, передавайте через ConfigMaps. Секреты храните отдельно в специализированной системе управления секретами.
Микросервисы
Для микросервисной архитектуры стоит рассмотреть Spring Cloud Config Server, чтобы централизовать управление конфигурацией, правилами ее использования и версионированием. Секреты при этом по-прежнему следует хранить в специализированной системе управления секретами.
Комментарий от Михаила Поливаха
В целом, Spring Cloud Config Server можно использовать для "хранения" секретов в том числе. Хранение тут в кавычках, т.е. Spring Cloud Config Server по сути выступает лишь некоторым прокси сервером для конфигуарции Spring Boot приложений
У Axelix, кстати, благодаря контрибьютеру из участников Spring АйО сообщества, появилась поддержка Spring Cloud Config Server, которая будет срелижена уже совсем скоро, в 1.1.0 minor релизе :)
Итоги
Эффективное управление конфигурацией приложения начинается с разумных значений по умолчанию, типобезопасных @ConfigurationProperties и проверки конфигурации при запуске. Значения, зависящие от среды, следует хранить вне приложения, учитывать приоритет источников свойств, а секреты в свою очередь помещать в специализированную систему управления секретами.
Подходящая стратегия конфигурации должна соответствовать архитектуре приложения и среде его развертывания.
Во время локальной разработки и удаленной отладки IntelliJ IDEA помогает увидеть фактическую конфигурацию: показывает итоговые значения свойств и их источники, подсвечивает переопределения и обеспечивает навигацию между конфигурационными файлами и связанными с ними Java-свойствами.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

