Как работает @SpringBootApplication
Если заглянуть в объявление @SpringBootApplication, мы увидим:
🟢 Junior Level
@SpringBootApplication — это главная композитная мета-аннотация, с которой начинается любое приложение на Spring Boot. Она устанавливается над классом, содержащим метод main(), и объединяет три важнейшие функции фреймворка.
package com.example.shop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}
3 составные части аннотации
Если заглянуть в объявление @SpringBootApplication, мы увидим:
@SpringBootConfiguration(специализированная версия@Configuration):- Объявляет класс источником определений бинов (
@Bean). Позволяет инструментам тестирования (@SpringBootTest) автоматически находить главную конфигурацию приложения.
- Объявляет класс источником определений бинов (
@EnableAutoConfiguration:- Активирует «магию» Spring Boot — автоматическую настройку компонентов (БД, безопасности, веб-сервера, сериализатора JSON) на основе обнаруженных в
classpathбиблиотек и настроек вapplication.yml.
- Активирует «магию» Spring Boot — автоматическую настройку компонентов (БД, безопасности, веб-сервера, сериализатора JSON) на основе обнаруженных в
@ComponentScan:- Включает автоматическое сканирование директорий в поисках классов, помеченных аннотациями
@Component,@Service,@Repository,@Controller, начиная с текущего пакета (com.example.shop) и во всех его дочерних подпакетах.
- Включает автоматическое сканирование директорий в поисках классов, помеченных аннотациями
Главное правило размещения: Всегда размещайте класс с
@SpringBootApplicationв корневом пакете вашего проекта (например,com.company.project). Тогда все сервисы, контроллеры и репозитории в подпакетах будут гарантированно обнаружены.
🟡 Middle Level
Параметры аннотации @SpringBootApplication
Аннотация служит удобным фасадом и перенаправляет свои параметры во внутренние аннотации:
@SpringBootApplication(
// Передаётся в @EnableAutoConfiguration: исключение ненужных автоконфигураций
exclude = {DataSourceAutoConfiguration.class},
// Передаётся в @ComponentScan: переопределение пакетов сканирования
scanBasePackages = {"com.example.shop", "com.example.common"},
// Типобезопасное указание пакетов через маркерные классы
scanBasePackageClasses = {CommonMarker.class},
// Передаётся в @SpringBootConfiguration: отключение CGLIB-прокси для конфигурации
proxyBeanMethods = true
)
public class ShopApplication { }
Сквозной пайплайн выполнения: SpringApplication.run(...)
Когда вызывается метод SpringApplication.run(Application.class, args), происходит строгая последовательность системных шагов:
1. Определение типа приложения (SERVLET, REACTIVE или NONE)
↓
2. Подготовка Environment (Загрузка application.yml, переменных ОС, аргументов CLI)
↓
3. Создание ApplicationContext (например, AnnotationConfigServletWebServerApplicationContext)
↓
4. Подготовка контекста (Применение ApplicationContextInitializer, регистрация Application.class)
↓
5. context.refresh() (Запуск ядра Spring: сканирование, создание бинов, запуск Tomcat)
↓
6. Запуск Runner-бинов (CommandLineRunner / ApplicationRunner)
↓
7. Публикация события ApplicationReadyEvent (Приложение полностью готово к трафику)
Запуск встроенного веб-сервера: фаза onRefresh()
Один из самых популярных вопросов: «На каком именно этапе поднимается встроенный Tomcat/Jetty?»
Веб-сервер запускается внутри фазы context.refresh(), а именно в защищенном методе onRefresh() класса ServletWebServerApplicationContext:
- Контейнер находит фабрику веб-сервера (
ServletWebServerFactory, по умолчаниюTomcatServletWebServerFactory). - Создается экземпляр
WebServer(Tomcat), конфигурируются коннекторы и открывается сетевой порт (по умолчанию 8080). - Это происходит до того, как создаются обычные бизнес-бины, но связывание DispatcherServlet завершается перед финальной публикацией готовности.
🔴 Senior Level
Фазы context.refresh() в деталях
Сердцем метода run() является вызов AbstractApplicationContext.refresh(). В контексте @SpringBootApplication критичны следующие шаги:
invokeBeanFactoryPostProcessors(beanFactory):- Вызывается
ConfigurationClassPostProcessor. - Парсится класс
@SpringBootApplication. - Запускается
@ComponentScan: через библиотеку ASM сканируются.class-файлы и создаютсяBeanDefinitionдля всех@Component. - Запускается
AutoConfigurationImportSelector: считывается.imports-файл, проверяются условия@Conditionalи регистрируются автоконфигурации.
- Вызывается
registerBeanPostProcessors(beanFactory):- Регистрируются все BPP (
AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor, AOP Proxy Creators).
- Регистрируются все BPP (
onRefresh():- Инициализируется и стартует встроенный WebServer (Tomcat).
finishBeanFactoryInitialization(beanFactory):- Метод
beanFactory.preInstantiateSingletons(): инстанцируются, наполняются зависимостями и инициализируются все синглтон-бины.
- Метод
finishRefresh():- Публикуется
ContextRefreshedEvent.
- Публикуется
Механизм FailureAnalyzer
Если на этапе старта контекста происходит ошибка (например, занят порт 8080 или не задан пароль к БД):
- Вместо гигантского пугающего стек-трейса на 500 строк Spring Boot перехватывает исключение через SPI-интерфейс
FailureAnalyzer. - Встроенные анализаторы (
PortInUseFailureAnalyzer,NoSuchBeanDefinitionFailureAnalyzer) форматируют понятный отчет:
===========================
APPLICATION FAILED TO START
===========================
Description:
Web server failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that is listening on port 8080 or configure this application to listen on another port.
Runner интерфейсы: CommandLineRunner vs ApplicationRunner
После того как контекст полностью поднят, Spring Boot выполняет все бины, реализующие один из runner-интерфейсов:
| Интерфейс | Метод | Особенности работы с аргументами CLI |
|---|---|---|
CommandLineRunner |
run(String... args) |
Передает сырой массив строк: ["--port=9090", "test"] |
ApplicationRunner |
run(ApplicationArguments args) |
Предоставляет удобный парсинг аргументов: args.getOptionValues("port"), args.getNonOptionArgs() |
@Component
public class StartupTasks implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
if (args.containsOption("seed-data")) {
System.out.println("Загрузка тестовых данных в базу...");
}
}
}
🎯 Шпаргалка для интервью
30-секундный ответ
@SpringBootApplication— мета-аннотация, объединяющая:
@SpringBootConfiguration: объявляет класс конфигурационным (с поддержкой@Bean) и служит маркером для тестов (@SpringBootTest).@EnableAutoConfiguration: запускает импорт автоконфигураций черезAutoConfigurationImportSelector.@ComponentScan: рекурсивно ищет компоненты Spring начиная с текущего пакета.Метод
SpringApplication.run()готовитEnvironment, создаетApplicationContext, в фазеonRefresh()поднимает встроенный веб-сервер (Tomcat), создает все синглтоны и в конце запускаетCommandLineRunner/ApplicationRunner.
4 каверзных вопроса с ответами
-
Что произойдет, если объявить класс с
@SpringBootApplicationв дефолтном пакете (без строкиpackage ...;)? Ответ:@ComponentScanбез указания базового пакета начинает сканировать текущий пакет. Если класс находится в корне (default package), Spring попытается просканировать абсолютно весь classpath, включая все подключенные JAR-зависимости. Это приведет к колоссальному падению производительности при старте, переполнению памяти или ошибкам загрузки сторонних классов. -
На каком этапе запуска приложения стартует встроенный Tomcat? Ответ: Внутри метода
context.refresh(), а именно в переопределенном методеonRefresh()классаServletWebServerApplicationContext. На этом этапе уже готовыBeanFactoryPostProcessor, но синглтон-бины бизнес-логики еще не инстанцированы. -
В чём разница между
CommandLineRunnerиApplicationRunner? Ответ: Оба интерфейса выполняются после завершения сборки контекста перед переходом в рабочий режим. Разница в сигнатуре:CommandLineRunnerпринимает сырой строковый массив аргументов (String... args), аApplicationRunnerпринимает объектApplicationArguments, умеющий удобно разделять именованные опции (--foo=bar) и позиционные аргументы. -
Зачем нужна аннотация
@SpringBootConfiguration, если уже есть стандартная@Configuration? Ответ:@SpringBootConfigurationявляется мета-аннотацией над@Configuration. Ее главное предназначение — служить уникальным маркером конфигурации Spring Boot, который автоматически ищется слайс-тестами (@DataJpaTest,@WebMvcTest,@SpringBootTest) для обнаружения корневой конфигурации приложения.
Красные флаги (чего категорически нельзя говорить)
- ❌ «
@SpringBootApplicationпросто заменяет методmain» — это композитная аннотация из конфигурации, компонент-сканирования и автоконфигурации. - ❌ «Встроенный Tomcat запускается до создания
ApplicationContext» — Tomcat запускается внутриcontext.refresh()(фазаonRefresh). - ❌ «
@SpringBootApplicationсканирует все папки на жестком диске» — сканируется строго текущий Java-пакет класса и его подпакеты. - ❌ «
CommandLineRunnerзапускается до инициализации бинов» — раннеры запускаются строго ПОСЛЕ того, как все синглтон-бины созданы и инициализированы.