Профили, @Value и внешняя конфигурация
Приложение не должно знать пароли и адреса заранее — они приходят извне. Spring читает настройки из конфигов и переключает поведение по профилям окружения.
Суть: @Value вставляет одно значение, @ConfigurationProperties маппит группу настроек в типизированный объект, а @Profile включает бины только в нужном окружении (dev/prod/test).
Одно и то же приложение работает на ноутбуке разработчика, на тестовом стенде и на проде — с разными базами, ключами и адресами. Зашивать эти значения в код нельзя. Spring предлагает вынести конфигурацию наружу и менять её без пересборки.
@Value — одно значение
@Service
public class MailService {
@Value("${app.mail.from}")
private String fromAddress;
@Value("${app.mail.retries:3}") // 3 — значение по умолчанию
private int retries;
}
Синтаксис ${...} подставляет значение из application.yml или переменной окружения. После двоеточия — значение по умолчанию.
@ConfigurationProperties — группа настроек
Когда настроек много, удобнее собрать их в один типизированный объект:
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(String from, int retries, String host) { }
app:
mail:
from: [email protected]
retries: 5
host: smtp.example.com
Spring сам разложит ветку app.mail по полям записи. Это типобезопасно: опечатка в имени поля поймается раньше.
Профили — переключение окружений
@Configuration
@Profile("dev")
public class DevDataConfig {
@Bean
public DataSource dataSource() {
return inMemoryH2(); // лёгкая база для разработки
}
}
Бин с @Profile("dev") создаётся только при активном профиле dev. Активный профиль задаётся в конфиге (spring.profiles.active) или переменной окружения.
Как работает под капотом
Spring собирает значения из множества источников в едином порядке приоритета: переменные окружения и аргументы командной строки перекрывают application.yml, профильные файлы (application-prod.yml) дополняют базовый. Активный профиль определяет, какие бины и файлы подключатся.
Источники конфигурации (по приоритету сверху вниз)
┌────────────────────────────────────┐
│ аргументы командной строки │ выше
│ переменные окружения │
│ application-{profile}.yml │
│ application.yml │ ниже
└────────────────────────────────────┘
| слияние с учётом приоритета
v
Environment -> @Value / @ConfigurationProperties
Смоделируем слияние конфигурации по приоритету:
# Слияние источников конфигурации: верхний перекрывает нижний
base = {"app.mail.from": "[email protected]", "app.mail.retries": "3"}
profile_prod = {"app.mail.retries": "10"}
env = {"app.mail.from": "[email protected]"}
def resolve(*sources):
# источники перечислены от низшего приоритета к высшему
merged = {}
for src in sources:
merged.update(src)
return merged
config = resolve(base, profile_prod, env)
print("from =", config["app.mail.from"]) # из env (высший приоритет)
print("retries =", config["app.mail.retries"]) # из профиля prod
Нажмите «Попробуй сам ▶»: значение из источника с высшим приоритетом побеждает — так же Spring разрешает настройки.
Частые ошибки
- Хранить секреты в репозитории. Пароли и ключи — в переменные окружения или секрет-хранилища, не в
application.ymlпод git. - Жёстко прописывать окружение. Не зашивайте «if prod» в код — используйте профили.
- Опечатка в ключе
@Value. Без значения по умолчанию приложение упадёт с ошибкой «could not resolve placeholder».
Best practices
- Для группы связанных настроек используйте
@ConfigurationProperties(типобезопасно) вместо россыпи@Value. - Разделяйте окружения профилями и файлами
application-{profile}.yml. - Секреты передавайте через переменные окружения; в git храните только нечувствительные дефолты.
Итог: внешняя конфигурация отвязывает код от окружения. @Value — для отдельных значений, @ConfigurationProperties — для групп, профили — для переключения dev/prod/test. Одна сборка работает везде.
Закрепим главное
Внешняя конфигурация воплощает принцип «build once, run anywhere»: один и тот же артефакт работает на ноутбуке, тестовом стенде и в проде, а различия задаются снаружи. Это резко упрощает деплой и исключает класс ошибок «собрали не ту сборку». Чем меньше окруженческих деталей зашито в код, тем гибче приложение.
На практике держитесь трёх ориентиров. Для отдельных значений берите @Value, для групп связанных настроек — типобезопасный @ConfigurationProperties, который ловит опечатки в именах раньше. Окружения разделяйте профилями и файлами application-{profile}.yml, а не условиями в коде. И самое важное — секреты: пароли, ключи, токены никогда не должны попадать в репозиторий. Их место — в переменных окружения или специальных хранилищах секретов, которые перекрывают значения из конфигурационных файлов по приоритету.