Як працює @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запускається до ініціалізації бінів» — раннери запускаються строго ПІСЛЯ того, як усі синглтон-біни створені та ініціалізовані.