При запуске Spring-приложения пакеты сканируются. Из аннотированных с помощью @Component, @Service, @Repository и @Configuration клаccов создаются бины. В этой статье рассмотрим, как задаются пакеты и классы для сканирования.
@ComponentScan в @SpringBootApplication
Сканирование происходит благодаря аннотации @ComponentScan. В Sping Boot приложении она явно не прописывается, но содержится внутри аннотации @SpringBootApplication, с помощью которой аннотирован главный класс приложения.
Аннотация @SpringBootApplication:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ... }
Сканируются все классы, которые находятся в том же пакете, что и класс с @ComponentScan (то есть с @SpringBootApplication), а также в подпакетах этого пакета.
Если сгенерировать Spring Boot проект на сайте https://start.spring.io, то видно: главный класс с аннотацией с @SpringBootApplication находится в корневом пакете. А значит то по умолчанию сканируются все классы, которые находятся ниже.
То есть в пакете ru.sysout MainApplication может находиться, а сразу в папке java без пакета — нет.
Корректная структура:
Классы Car и Engine у нас компоненты — они аннотированы с помощью @Component. Поскольку они лежат в структуре пакетов ниже главного класса, они успешно сканируются и на их основе создаются бины.
Пакеты, параллельные пакету с главным классом @SpringBootApplication, не сканируются
Но ситуации бывают разные. Попробуем переложить Car в другой пакет, который находится не в подпакете относительно MainApplication, а параллельно ему.
Теперь Car не сканируется и бин не создается. В этом можно убедиться, если попытаться извлечь бин типа Car из ApplicationContext:
@SpringBootApplication public class MainApplication { public static void main(String[] args) { ApplicationContext applicationContext=SpringApplication.run(MainApplication.class, args); Car car=applicationContext.getBean(Car.class); } }
В консоли появляется ошибка:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'car.Car' available
И хотя такая структура пакетов не является стандартной, ошибку можно исправить с помощью @Import.
Аннотация @Import
С помощью @Import можно добавить классы в перечень сканируемых, даже если они лежат в другом пакете.
Чтобы просканировать Car, рядом с @SpringBootApplication добавим аннотацию @Import:
@SpringBootApplication @Import({Car.class}) public class MainApplication { public static void main(String[] args) { ApplicationContext applicationContext=SpringApplication.run(MainApplication.class, args); Car car=applicationContext.getBean(Car.class); } }
В @Import можно передать массив классов, но в нашем примере достаточно одного Car.
Исходный код
Код примера есть на GitHub.