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

@SpringBootApplication объединяет три аннотации: @SpringBootConfiguration + @ComponentScan + @EnableAutoConfiguration. Первые две очевидны. Третья — магия, которая объясняет, почему JdbcTemplate, MongoTemplate, KafkaTemplate появляются в контексте сами, как только вы добавили starter.

Как работает auto-configuration

Старая модель (до Boot 2.7) — через spring.factories. Новая (с 2.7+, стандарт в 3.x) — через файл META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, в котором перечислены классы auto-configuration.

# spring-boot-autoconfigure-3.x.jar содержит:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
... (сотни классов)

Каждый класс — обычный @Configuration с @Bean-методами, обвешанный @ConditionalOn*-аннотациями:

@AutoConfiguration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "spring.datasource.url")
    public DataSource dataSource(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

При старте Boot читает все AutoConfiguration.imports, прогоняет условия (@ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty), оставляет только те, что соответствуют, и регистрирует их @Bean-ы. Бины пользователя имеют приоритет — @ConditionalOnMissingBean пропускает auto-configuration, если вы уже объявили свой.

Посмотреть, что было применено:

debug=true

И в логах появится CONDITIONS EVALUATION REPORTPositive matches / Negative matches. Это первое место, куда смотреть, когда «почему-то бин не создался».

Externalized configuration

Источники свойств в порядке приоритета (от высокого к низкому):

  1. Command-line arguments: --server.port=9000
  2. SPRING_APPLICATION_JSON env var
  3. ServletConfig init params
  4. JNDI
  5. System properties (-Dserver.port=9000)
  6. OS environment (SERVER_PORT=9000)
  7. application-{profile}.properties / .yml
  8. application.properties / .yml
  9. @PropertySource-аннотации
  10. Default properties (SpringApplication.setDefaultProperties)

В практике: prod-конфиг — через env vars (Kubernetes ConfigMap/Secret), dev — application.yml в репозитории, локальный override — application-local.yml.gitignore).

@ConfigurationProperties vs @Value

@Value — для одиночного значения с поддержкой SpEL:

@Value("${app.timeout:5000}")  // default 5000
private long timeoutMs;

@ConfigurationProperties — для группы свойств с type-safe биндингом:

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

@Configuration
@EnableConfigurationProperties(RetryProperties.class)
public class AppConfig { }

@Service
public class OrderService {
    public OrderService(RetryProperties props) { /* ... */ }
}

Правило: больше одного-двух связанных свойств → @ConfigurationProperties. Это даёт валидацию (@Validated), IDE-completion в YAML (через annotation processor spring-boot-configuration-processor), и метаданные для Spring Boot Configuration Metadata.

Валидация конфигурации

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

При старте Boot валидирует, если что-то не соответствует — приложение падает с понятной ошибкой, а не позже в рантайме на NPE.

Профили

Профиль — пометка на бине/конфигурации, активная или нет. Бины с неактивным профилем в контекст не попадают.

@Service
@Profile("prod")
public class RealEmailService implements EmailService { ... }

@Service
@Profile({"dev", "test"})
public class FakeEmailService implements EmailService { ... }

Активация:

  • spring.profiles.active=prod,metrics-on (несколько одновременно).
  • SPRING_PROFILES_ACTIVE=prod (env var).
  • --spring.profiles.active=prod (CLI).

application-{profile}.yml подгружается поверх application.yml для активных профилей. Полезно для per-environment различий: application-prod.yml, application-test.yml.

@Profile на @Configuration vs условные бины

Если выбор между двумя реализациями завязан на переключатель — лучше @ConditionalOnProperty, а не @Profile:

@Service
@ConditionalOnProperty(name = "email.backend", havingValue = "ses")
public class SesEmailService implements EmailService { ... }

@Service
@ConditionalOnProperty(name = "email.backend", havingValue = "smtp", matchIfMissing = true)
public class SmtpEmailService implements EmailService { ... }

Свойство explicit, документируется в application.yml, легко переопределяется без перепаковки артефакта.

Custom starter

Когда у вас есть переиспользуемая инфраструктура (библиотека для метрик, обвязка над брокером, security-фильтры), оформляйте её как starter — это снимает burden настройки с пользователей.

Структура минимального starter'а:

my-starter/
├── build.gradle.kts
├── src/main/java/com/example/mystarter/
│   ├── MyAutoConfiguration.java
│   └── MyProperties.java
└── src/main/resources/META-INF/spring/
    └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService(MyProperties props) {
        return new MyService(props.getApiUrl(), props.getTimeoutMs());
    }
}
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.mystarter.MyAutoConfiguration

Пользователь добавляет одну зависимость, и MyService появляется в контексте сам. Параметры — через my.api-url=... и my.timeout-ms=....

В open-source — это то, что библиотека usecase-pattern даёт для UseCase/Handler/Dispatcher: starter подключается одной зависимостью, auto-config регистрирует Dispatcher, метрики, validators.

Configuration metadata для IDE

При @ConfigurationProperties подключите annotation processor:

dependencies {
    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
}

После сборки в META-INF/spring-configuration-metadata.json появится JSON с описаниями свойств, типов, default'ов. IntelliJ читает его и даёт autocomplete для YAML/properties.

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

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