Spring @Import注解

2023/05/13

1. 概述

在本文中,我们将学习如何使用Spring的@Import注解,同时阐明它与@ComponentScan的不同之处

2. 配置和Beans

在介绍@Import注解之前,我们需要了解什么是Spring Bean,并且对@Configuration注解有一个基本的认识。

假设我们已经准备了三个bean,Bird、Cat和Dog,每个bean都有自己的配置类。

然后,我们可以为我们的上下文提供这些配置类


@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) {
        assertTrue(context.containsBean(beanName));
        assertNotNull(context.getBean(beanClass));
    }
}

3. 使用@Import对配置进行分组

声明所有配置类并没有问题,但是想象一下在不同的源中控制几十个配置类的麻烦。应该有更好的方法。

@Import注解有一个解决方案,它可以对配置类进行分组:


@Configuration
@Import({DogConfig.class, CatConfig.class})
class MammalConfiguration {

}

现在,我们只需要指定MammalConfiguration:


@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {MammalConfiguration.class})
class MammalConfigUnitTest {

    @Autowired
    ApplicationContext context;

    @Test
    void givenImportedBeans_whenGettingEach_shallFindOnlyTheImportedBeans() {
        assertThatBeanExists("dog", Dog.class);
        assertThatBeanExists("cat", Cat.class);
        assertFalse(context.containsBean("bird"));
    }

    private void assertThatBeanExists(String beanName, Class<?> beanClass) {
        assertTrue(context.containsBean(beanName));
        assertNotNull(context.getBean(beanClass));
    }
}

同时,我们还可以再做一组来包含所有的动物配置类


@Configuration
@Import({MammalConfiguration.class, BirdConfig.class})
class AnimalConfiguration {

}

这不会缺少任何Bean的配置,我们只需要指定一个配置类AnimalConfiguration:


@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AnimalConfiguration.class})
class AnimalConfigUnitTest {

    @Autowired
    ApplicationContext context;

    @Test
    void givenImportedBeans_whenGettingEach_shallFindOnlyTheImportedBeans() {
        assertThatBeanExists("dog", Dog.class);
        assertThatBeanExists("cat", Cat.class);
        assertThatBeanExists("bird", Cat.class);
    }

    private void assertThatBeanExists(String beanName, Class<?> beanClass) {
        assertTrue(context.containsBean(beanName));
        assertNotNull(context.getBean(beanClass));
    }
}

4. @Import与@ComponentScan

在继续讲解@Import示例之前,让我们将其与@ComponentScan进行比较。

4.1 相似之处

两个注解都可以接收任何@Component或@Configuration类

让我们使用@Import添加一个新的@Component:


@Configuration
@Import(Bug.class)
class BugConfig {

}

@Component(value = "bug")
class Bug {

}

现在,Bug bean就像任何其他bean一样可用。

4.2 概念差异

简而言之,我们可以使用两个注解实现相同的效果。那么,它们之间有什么区别吗?

为了回答这个问题,让我们记住,Spring通常提倡约定优于配置的方法。

与我们的注解类比,@ComponentScan更像约定,而@Import看起来像配置

4.3 实际应用程序中会发生什么

通常,我们在根包中使用@ComponentScan启动我们的应用程序,以便它可以为我们找到所有组件。 如果我们使用的是Spring Boot,那么@SpringBootApplication已经包含了@ComponentScan,我们就可以使用了。这就是约定的好处。

现在,让我们想象一下我们的应用程序正在大量增长。 现在我们需要处理来自所有不同地方的bean,比如组件、不同的包结构以及我们自己和第三方构建的模块。

在这种情况下,将所有内容添加到上下文中可能会引发关于使用哪个bean的冲突。除此之外,我们的启动时间可能会很缓慢。

另一方面,我们不想为每个新组件编写@Import,因为这样做会适得其反

以我们的动物为例。我们确实可以从上下文声明中隐藏导入,但我们仍然需要记住每个配置类的@Import。

4.4 一起使用

我们可以两全其美。让我们想象一下,我们有一个package仅包含我们的动物类。它也可以是一个组件或模块。

然后我们可以为我们的动物类包创建一个@ComponentScan

package cn.tuyucheng.taketoday.importannotation.animal;

// imports...

@Configuration
@ComponentScan
public class AnimalScanConfiguration {

}

还有一个@Import来控制我们将添加到上下文中的内容:

package cn.tuyucheng.taketoday.importannotation.zoo;

// imports...

@Configuration
@Import(AnimalScanConfiguration.class)
class ZooApplication {

}

最后,任何添加到animal包中的新bean都会被我们的上下文自动找到。而且我们仍然可以明确控制我们正在使用的配置。

5. 总结

在本文中,我们学习了如何使用@Import来组织我们的配置。

我们还了解到,@Import与@ComponentScan非常相似,只是 @Import使用显式方法,而@ComponentScan使用隐式方法

此外,我们还研究了在实际应用程序中控制我们的配置可能存在的困难,以及如何通过结合两个注解来处理这些问题。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章