Що робить анотація @ComponentScan
У Spring Boot ця анотація автоматично входить до складу мета-анотації @SpringBootApplication.
🟢 Junior Level
@ComponentScan — це анотація Spring, яка вказує IoC-контейнеру, в яких пакетах (packages) необхідно шукати класи, позначені стереотипними анотаціями (@Component, @Service, @Repository, @Controller, @Configuration), щоб автоматично зареєструвати їх як біни в ApplicationContext.
У Spring Boot ця анотація автоматично входить до складу мета-анотації @SpringBootApplication.
package com.example.shop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// Автоматично запускає сканування пакета com.example.shop та всіх його підпакетів:
// com.example.shop.service, com.example.shop.controller, com.example.shop.repository
@SpringBootApplication
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}
Ручне налаштування
Якщо ваші сервіси або спільні бібліотеки знаходяться за межами кореневого пакета додатка, сканування налаштовується вручну:
@Configuration
@ComponentScan(basePackages = {"com.example.shop", "com.example.common"})
public class CustomScanConfig { }
🟡 Middle Level
Параметри анотації @ComponentScan
Анотація надає багатий набір параметрів для гнучкої фільтрації компонентів:
@Configuration
@ComponentScan(
// Рядкове зазначення пакетів
basePackages = "com.example.service",
// Типобезпечне зазначення пакетів через маркерні класи (Best Practice)
basePackageClasses = {ServiceMarker.class, RepositoryMarker.class},
// Виключення класів за типом або анотацією
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = LegacyBillingService.class
),
// Додавання кастомних анотацій у сканування
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = CustomComponent.class
),
// Вимкнення дефолтних стереотипів (@Component, @Service тощо)
useDefaultFilters = true
)
public class AdvancedScanConfig { }
Типи фільтрів (FilterType)
Spring підтримує 5 стратегій фільтрації кандидатів у біни:
FilterType.ANNOTATION— перевірка наявності анотації над класом.FilterType.ASSIGNABLE_TYPE— перевірка успадкування або реалізації зазначеного класу/інтерфейсу.FilterType.REGEX— зіставлення імені класу з регулярним виразом (наприклад,.*Stub.*).FilterType.ASPECTJ— вибір за AspectJ-виразом типу.FilterType.CUSTOM— реалізація користувацького інтерфейсуorg.springframework.core.type.filter.TypeFilter.
Чому basePackageClasses надійніше за basePackages
Зазначення базових пакетів рядками (basePackages = "com.example.service") загрожує прихованими багами:
- Під час рефакторингу та перейменування пакетів в IDE рядкові літерали не оновлюються автоматично.
- Помилка в назві пакета виявиться лише під час старту додатка (
NoSuchBeanDefinitionException).
Використання basePackageClasses спирається на посилання на клас (зазвичай порожній інтерфейс-маркер):
@ComponentScan(basePackageClasses = OrderServiceMarker.class)
Під час рефакторингу IDE автоматично перенесе імпорт класу, а будь-яка друкарська помилка призведе до помилки компіляції ще до запуску тестів.
🔴 Senior Level
Внутрішній механізм: як працює ASM і чому класи НЕ завантажуються в JVM
Багато розробників помилково вважають, що Spring знаходить біни за допомогою звичайної Java-рефлексії (Class.forName()).
Чому рефлексія не використовується:
- Завантаження сотень сторонніх
.classфайлів у пам’ять JVM ClassLoader-ом засмічує область пам’яті Metaspace. - Під час завантаження класу JVM зобов’язана виконати статичні блоки ініціалізації (
static { ... }). Якщо в статичному блоці є звернення до бази даних або файлу, додаток впаде задовго до готовності контексту.
Як сканує Spring (ClassPathBeanDefinitionScanner):
- На етапі
invokeBeanFactoryPostProcessorsзапускаєтьсяConfigurationClassPostProcessor. - Сканер перетворює ім’я пакета на шлях пошуку ресурсів:
classpath*:com/example/**/*.class. - Для кожного знайденого файлу викликається фабрика
SimpleMetadataReaderFactory. - За допомогою бібліотеки ASM Spring зчитує байткод
.classфайлу як масив байтів та аналізує заголовки константного пулу (Constant Pool). - Витягуються метадані: ім’я класу, суперклас, інтерфейси та наявність анотацій, у яких
RetentionPolicy = RUNTIME. - Формується легковажний дескриптор
ScannedGenericBeanDefinition. - Клас завантажується в JVM ClassLoader тільки в самий останній момент — коли викликається
createBeanInstance()для створення об’єкта в купі!
Оптимізація холодного старту: spring-context-indexer
У великих мікросервісах із тисячами класів рекурсивний обхід файлової системи та сканування JAR-архівів через ASM може займати від 5 до 15 секунд під час кожного старту.
Рішення: Попередня індексація на етапі збирання
Додайте в pom.xml анотаційний процесор:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>
Під час компіляції (javac):
spring-context-indexer аналізує проєкт і генерує маніфест:
META-INF/spring.components
Вміст файлу:
com.example.service.UserService=org.springframework.stereotype.Service
com.example.repository.OrderRepository=org.springframework.stereotype.Repository
Під час старту додатка:
ClassPathScanningCandidateComponentProvider виявляє CandidateComponentsIndex.
Замість сканування файлової системи Spring миттєво зчитує готовий список!
Час старту скорочується в 2-4 рази.
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь
@ComponentScanзадає базові пакети для автоматичного виявлення компонентів Spring (@Component,@Service,@Repository,@Controller,@Configuration). Входить до складу@SpringBootApplication, скануючи за замовчуванням поточний пакет та всі його дочірні директорії.
- Під капотом керується компонентом
ClassPathBeanDefinitionScanner.- Ключова особливість: сканування виконується за допомогою низькорівневого аналізу байткоду через бібліотеку ASM, без фактичного завантаження класів у пам’ять JVM і без виконання статичних блоків.
- Для безпечного рефакторингу рекомендується використовувати
basePackageClasses, а для прискорення старту великих проєктів —spring-context-indexer.
4 каверзних питання з відповідями
-
Чи завантажує Spring класи в пам’ять JVM ClassLoader під час роботи
@ComponentScan? Відповідь: Ні, не завантажує. Spring використовує бібліотеку ASM (MetadataReader) для читання байткоду скомпільованих файлів.classяк потоку байтів. Це дозволяє прочитати анотації та сигнатури типів без завантаження класів у Metaspace і запобігає небажаному виконанню статичних блоків ініціалізації (static {}). Клас завантажується ClassLoader-ом лише на етапі безпосереднього інстанціювання біна. -
Чим
basePackageClassesкращий за параметрbasePackages? Відповідь:basePackagesприймає рядкові літерали пакетів. При перейменуванні пакетів через IDE рядкові значення легко пропустити, що призведе до помилок у рантаймі.basePackageClassesприймає посилання на Java-класи (.class), забезпечуючи сувору перевірку типів під час компіляції та автоматичний рефакторинг. -
Що робить анотаційний процесор
spring-context-indexer? Відповідь: Під час компіляції проєкту він сканує анотації та генерує статичний індексний файлMETA-INF/spring.components. При старті додатка Spring зчитує цей файл напряму, повністю вимикаючи повільне сканування файлової системи та JAR-архівів через ASM, що радикально прискорює холодний старт. -
Як за допомогою
@ComponentScanвимкнути реєстрацію всіх стандартних@Componentі реєструвати лише свої анотації? Відповідь: Виставити прапорецьuseDefaultFilters = falseта задати потрібні анотації в параметріincludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyAnnotation.class).
Червоні прапорці (чого категорично не можна говорити)
- ❌ «
@ComponentScanвикористовує стандартну Java Reflection для пошуку файлів на диску» — рефлексія не вміє шукати класи в пакетах; пошук виконується черезResourcePatternResolverта ASM. - ❌ «Якщо клас знаходиться у відсканованому пакеті, його
static-блок виконається під час старту» — static-блок виконується лише під час ініціалізації класу ClassLoader-ом, а не під час ASM-сканування. - ❌ «
@ComponentScanнеобхідно завжди явно ставити над кожним сервісом» — анотація встановлюється один раз над головним конфігураційним класом (або успадковується з@SpringBootApplication). - ❌ «
spring-context-indexerсканує біни під час виконання HTTP-запитів» — це процесор компіляції, що генерує статичний індекс заздалегідь.