← назад к разделу

Главная фишка Spring Boot — он почти ничего не заставляет настраивать руками. Добавили одну зависимость для работы с базой — и JdbcTemplate уже есть. Добавили зависимость для веба — и приложение само поднимает встроенный сервер. Разберём с нуля, как это устроено, откуда приложение берёт настройки и как держать разные настройки для разработки и для боевого окружения.

Стартеры — наборы зависимостей одним пунктом

Раньше, чтобы подключить, скажем, веб-слой, приходилось вручную перечислять десяток библиотек: сам Spring MVC, сервер, библиотеку для JSON, валидацию — и следить, чтобы их версии были совместимы. Одна несовместимая версия — и приложение падает на старте с непонятной ошибкой. Это долго и хрупко.

Стартер (starter) — это готовый набор зависимостей под одну задачу, собранный за вас. Подключаете один пункт — получаете всё нужное в согласованных версиях.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")  // веб целиком
    implementation("org.springframework.boot:spring-boot-starter-data-jpa") // работа с БД
}

Стартеры легко узнать по имени: они начинаются с spring-boot-starter-. Внутри стартера нет почти никакого кода — это просто список других библиотек. А вот настраивает их в рабочее состояние уже следующая часть.

Как Spring Boot сам настраивает бины

Подключили стартер для базы — и в приложении откуда-то появился готовый DataSource и JdbcTemplate, хотя вы их не создавали. Раньше каждый такой объект пришлось бы собирать руками: прочитать адрес базы, логин, пароль, создать пул соединений, связать всё вместе. Десятки строк однотипной настройки в каждом проекте.

Auto-configuration — это механизм, которым Spring Boot делает эту настройку за вас. Идея простая: «посмотри, какие библиотеки лежат в проекте, и настрой их разумным образом по умолчанию».

Включается всё одной аннотацией на главном классе:

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

@SpringBootApplication — это три аннотации в одной: пометка класса как конфигурации, сканирование ваших пакетов на @Component и @Service, и @EnableAutoConfiguration — та самая команда «настрой всё, что найдёшь в проекте».

Внутри стартеров лежат заранее написанные классы настройки. При старте Spring Boot перебирает их и для каждого решает: применять или нет.

Условные аннотации — настройка «если есть»

Откуда Spring Boot знает, что именно настраивать? Ведь в одном проекте есть база, а в другом — нет, и настройка базы там только мешала бы.

Ответ — условные аннотации. Каждый класс настройки помечен условиями, и применяется он, только если условия выполнены.

@AutoConfiguration
@ConditionalOnClass(DataSource.class)              // только если класс DataSource есть в проекте
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean                       // только если вы не создали свой DataSource
    @ConditionalOnProperty(name = "spring.datasource.url") // только если задан адрес базы
    public DataSource dataSource(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

Самые частые условия и их смысл:

  • @ConditionalOnClass — применить, только если нужный класс есть в проекте. Нет библиотеки для базы — настройка базы просто пропускается.
  • @ConditionalOnMissingBean — применить, только если такого бина ещё нет. Это значит ваши настройки всегда главнее: объявили свой DataSource — Spring Boot отступает и оставляет ваш.
  • @ConditionalOnProperty — применить, только если в настройках стоит определённое значение. Удобно, чтобы включать и выключать что-то одной строкой.

Итог: вы кладёте в проект зависимости, а Spring Boot собирает из них работающий контекст, не мешая там, где вы что-то задали сами.

Почему бин не создался

Когда «должен был появиться бин, а его нет», не нужно гадать. Включите режим отладки:

debug=true

В логах при старте появится отчёт о принятых решениях: что подошло (совпавшие условия) и что отклонено и почему. Это первое место, куда стоит смотреть.

Настройки снаружи кода

Адрес базы, пароли, номер порта нельзя зашивать в код: для разработки одно значение, для боевого сервера другое, а пароли вообще не должны лежать в репозитории. Менять код и пересобирать приложение ради смены порта — плохой путь.

Spring Boot позволяет держать все такие значения снаружи — в файле настроек или в переменных окружения. Основной файл — application.yml (или application.properties) в проекте:

server:
  port: 8080
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/shop

Одно и то же значение можно задать в нескольких местах, и у них есть приоритет. Запоминать весь список не нужно, достаточно главного правила: чем «ближе» к запуску задано значение, тем оно главнее. Аргумент командной строки перебивает переменную окружения, а та — файл application.yml.

java -jar app.jar --server.port=9000   # перебьёт порт из application.yml

На практике обычно так: общие настройки лежат в application.yml в репозитории; пароли и адреса для боевого сервера приходят через переменные окружения; а личные локальные правки — в application-local.yml, который не попадает в репозиторий.

@ConfigurationProperties и @Value

Настройки из файла нужно как-то прочитать в коде. Есть два способа.

@Value подходит для одного значения:

@Value("${app.timeout:5000}")   // 5000 — значение по умолчанию, если в настройках пусто
private long timeoutMs;

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

@ConfigurationProperties связывает целую группу настроек с объектом — одним махом и с проверкой типов:

app:
  retries:
    max-attempts: 3
    backoff-ms: 1000
    timeout-ms: 5000
@ConfigurationProperties(prefix = "app.retries")
public record RetryProperties(int maxAttempts, long backoffMs, long timeoutMs) {}

Теперь RetryProperties можно внедрить как обычную зависимость, и все три значения уже на месте. Имена в YAML записываются через дефис (max-attempts), а в коде — как обычные поля (maxAttempts); Spring сам сопоставляет одно с другим.

Простое правило: одно-два значения — @Value; группа связанных настроек — @ConfigurationProperties.

Проверка настроек на старте

К группе настроек можно добавить правила, и тогда Spring проверит их при запуске:

@ConfigurationProperties(prefix = "app.retries")
@Validated
public record RetryProperties(
    @Min(1) @Max(10) int maxAttempts,
    @Positive long backoffMs,
    @Positive long timeoutMs
) {}

Если кто-то задаст max-attempts: 0, приложение не запустится и сразу скажет, что не так. Это лучше, чем поймать ошибку позже, когда приложение уже работает и падает в неожиданном месте.

Профили — разные настройки для разных окружений

Для разработки вы хотите отправлять письма «понарошку» и писать подробные логи. На боевом сервере — отправлять настоящие письма и логировать сдержанно. Держать для этого разные сборки приложения неудобно и опасно.

Профиль — это пометка-режим. Бин или настройку можно привязать к профилю, и работать она будет только тогда, когда этот профиль включён.

@Service
@Profile("prod")
public class RealEmailService implements EmailService { }   // только на боевом

@Service
@Profile({"dev", "test"})
public class FakeEmailService implements EmailService { }   // в разработке и в тестах

Включить профиль можно настройкой spring.profiles.active:

spring:
  profiles:
    active: prod

Или через переменную окружения SPRING_PROFILES_ACTIVE=prod, или аргументом запуска --spring.profiles.active=prod. Можно включить сразу несколько через запятую.

Под профили есть и отдельные файлы настроек. Файл application-prod.yml накладывается поверх общего application.yml, когда активен профиль prod. Так удобно держать общие значения в одном месте, а различия между окружениями — в файлах по профилям: application-dev.yml, application-prod.yml.

Своя auto-configuration в библиотеке

Допустим, у вас есть переиспользуемый кусок инфраструктуры — обёртка над метрики или общий набор фильтров, — и вы хотите подключать его в разные проекты одной зависимостью, без копирования настроек. Тогда стоит оформить его как собственный стартер: он сам себя настроит у того, кто его подключил.

Принцип ровно тот же, что у встроенных стартеров: класс настройки с условными аннотациями плюс один служебный файл, который говорит Spring Boot «вот мой класс настройки, примени его».

@AutoConfiguration
@ConditionalOnClass(MyService.class)
@ConditionalOnMissingBean(MyService.class)
public class MyAutoConfiguration {

    @Bean
    public MyService myService() {
        return new MyService();
    }
}

Файл-указатель кладётся в src/main/resources по фиксированному пути META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, и в нём — просто имя вашего класса:

com.example.mystarter.MyAutoConfiguration

После этого любой, кто добавит вашу библиотеку, получит MyService в контексте автоматически — и точно так же сможет переопределить его своим бином благодаря @ConditionalOnMissingBean. Именно так устроена, например, библиотека usecase-pattern: одна зависимость — и нужные бины уже на месте.

Коротко

  • Стартер — готовый набор зависимостей под одну задачу в согласованных версиях; узнаётся по имени spring-boot-starter-*.
  • Auto-configuration — механизм, который сам настраивает бины, глядя на подключённые библиотеки; включается через @SpringBootApplication.
  • Условные аннотации решают, применять ли настройку: @ConditionalOnClass (есть ли класс), @ConditionalOnMissingBean (нет ли уже бина), @ConditionalOnProperty (стоит ли значение).
  • Ваши собственные бины всегда главнее автоматических — за это отвечает @ConditionalOnMissingBean.
  • Не создался бин — поставьте debug=true и посмотрите отчёт о принятых решениях при старте.
  • Настройки держат снаружи кода (application.yml, переменные окружения); чем ближе значение к запуску, тем оно главнее.
  • @Value — для одного значения; @ConfigurationProperties — для группы связанных настроек с проверкой типов.
  • @Validated на группе настроек роняет приложение на старте при неверном значении — ошибку видно сразу.
  • Профили дают разные настройки для разработки и боевого окружения; application-<профиль>.yml накладывается поверх общего файла.

Что почитать дальше

  • DI/IoC, bean lifecycle, scopes — как Spring создаёт бины, на которых строится auto-configuration.
  • Spring AOP — @EnableAsync, @EnableCaching, @EnableTransactionManagement тоже включаются через auto-configuration.
  • Spring Testing — как проверять @ConfigurationProperties и профили в тестах.
  • Библиотека usecase-pattern — пример реального стартера со своей auto-configuration.