Профили, @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, а не условиями в коде. И самое важное — секреты: пароли, ключи, токены никогда не должны попадать в репозиторий. Их место — в переменных окружения или специальных хранилищах секретов, которые перекрывают значения из конфигурационных файлов по приоритету.

Проверьте себя
1. Для чего удобнее @ConfigurationProperties по сравнению с россыпью @Value?
AДля ускорения старта приложения
BДля типобезопасного маппинга группы связанных настроек в один объект
CДля отключения профилей
DЭто устаревший механизм
2. Что делает бин, помеченный @Profile(\"dev\")?
AСоздаётся всегда
BСоздаётся только когда активен профиль dev
CУдаляет другие бины
DРаботает только в проде