1. Обзор
В этом руководстве мы узнаем , как использовать аннотацию Spring @Import
, а также объясним , чем она отличается от @ComponentScan .
2. Конфигурация и компоненты
Прежде чем понять аннотацию @Import
, нам нужно знать, что такое Spring Bean, и иметь базовые практические знания об аннотации @ Configuration .
Обе темы выходят за рамки данного руководства. Тем не менее, мы можем узнать о них в нашей статье о Spring Bean и в документации Spring .
Предположим, что мы уже подготовили три bean-компонента — Bird
, Cat
и Dog
— каждый со своим классом конфигурации.
Затем мы можем предоставить наш контекст с этими классами конфигурации
:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { BirdConfig.class, CatConfig.class, DogConfig.class })
class ConfigUnitTest {
@Autowired
ApplicationContext context;
@Test
void givenImportedBeans_whenGettingEach_shallFindIt() {
assertThatBeanExists("dog", Dog.class);
assertThatBeanExists("cat", Cat.class);
assertThatBeanExists("bird", Bird.class);
}
private void assertThatBeanExists(String beanName, Class<?> beanClass) {
Assertions.assertTrue(context.containsBean(beanName));
Assertions.assertNotNull(context.getBean(beanClass));
}
}
3. Группировка конфигураций с помощью @Import
Нет проблем с объявлением всех конфигураций. Но представьте себе проблему управления десятками классов конфигурации в разных источниках . Должен быть лучший способ.
У аннотации @ Import
есть решение, позволяющее группировать классы конфигурации :
@Configuration
@Import({ DogConfig.class, CatConfig.class })
class MammalConfiguration {
}
Теперь нам просто нужно вспомнить млекопитающих
:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { MammalConfiguration.class })
class ConfigUnitTest {
@Autowired
ApplicationContext context;
@Test
void givenImportedBeans_whenGettingEach_shallFindOnlyTheImportedBeans() {
assertThatBeanExists("dog", Dog.class);
assertThatBeanExists("cat", Cat.class);
Assertions.assertFalse(context.containsBean("bird"));
}
private void assertThatBeanExists(String beanName, Class<?> beanClass) {
Assertions.assertTrue(context.containsBean(beanName));
Assertions.assertNotNull(context.getBean(beanClass));
}
}
Что ж, наверное, мы скоро забудем нашу Птицу
, так что давайте сделаем еще одну группу, чтобы включить в нее все классы конфигурации животных
:
@Configuration
@Import({ MammalConfiguration.class, BirdConfig.class })
class AnimalConfiguration {
}
Наконец-то никто не остался в стороне, и нам достаточно запомнить один класс:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { AnimalConfiguration.class })
class AnimalConfigUnitTest {
// same test validating that all beans are available in the context
}
4. @Import
против @ComponentScan
Прежде чем перейти к примерам @ Import
, давайте остановимся и сравним его с @ ComponentScan
.
4.1. Сходства
Обе аннотации могут принимать любой класс @Component
или @Configuration
.
Давайте добавим новый @Component
, используя @Import
:
@Configuration
@Import(Bug.class)
class BugConfig {
}
@Component(value = "bug")
class Bug {
}
Теперь bean-компонент Bug
доступен так же, как и любой другой bean-компонент.
4.2. Концептуальная разница
Проще говоря, мы можем достичь одного и того же результата с обеими аннотациями . Итак, есть ли между ними разница?
Чтобы ответить на этот вопрос, давайте вспомним, что Spring обычно продвигает подход , основанный на соглашениях, а не на конфигурации .
Проводя аналогию с нашими аннотациями, @ComponentScan больше похож на условность, а @Import
—
на конфигурацию `` .
4.3. Что происходит в реальных приложениях
Обычно мы запускаем наши приложения с помощью @ComponentScan
в корневом пакете, чтобы он мог найти для нас все компоненты. Если мы используем Spring Boot, то @SpringBootApplication
уже включает @ComponentScan
, и все готово. Это показывает силу условностей.
Теперь давайте представим, что наше приложение сильно растет. Теперь нам нужно иметь дело с bean-компонентами из разных мест, такими как компоненты, различные структуры пакетов и модули, созданные нами или третьими сторонами.
В этом случае добавление всего в контекст рискует вызвать конфликты по поводу того, какой компонент использовать. Кроме того, мы можем получить медленное время запуска.
С другой стороны, мы не хотим писать @ Import
для каждого нового компонента , потому что это контрпродуктивно.
Возьмем, к примеру, наших животных. Мы действительно могли бы скрыть импорт из объявления контекста, но нам все равно нужно помнить @ Import
для каждого класса Config .
4.4. Работать вместе
Мы можем стремиться к лучшему из обоих миров. Представим, что у нас есть посылка только для наших животных
. Это также может быть компонент или модуль, сохраняющий ту же идею.
Тогда у нас может быть один @ComponentScan только
для нашего пакета с животными
:
package com.foreach.importannotation.animal;
// imports...
@Configuration
@ComponentScan
public class AnimalScanConfiguration {
}
И @Import
, чтобы сохранить контроль над тем, что мы добавим в контекст:
package com.foreach.importannotation.zoo;
// imports...
@Configuration
@Import(AnimalScanConfiguration.class)
class ZooApplication {
}
Наконец, любой новый компонент, добавленный в пакет животных, будет автоматически найден нашим контекстом. И у нас по-прежнему есть явный контроль над используемыми конфигурациями.
5. Вывод
В этом кратком руководстве мы узнали, как использовать @Import
для организации наших конфигураций.
Мы также узнали, что @Import
очень похож на @ComponentScan
, за исключением того факта, что @Import имеет явный подход, а @ComponentScan
использует неявный
.
Кроме того, мы рассмотрели возможные трудности с управлением нашими конфигурациями в реальных приложениях и способы их решения путем объединения обеих аннотаций.
Как обычно, полный код доступен на GitHub .