Spring @Component注解

2023/05/13

1. 概述

在本文中,我们将全面介绍Spring的@Component注解。 我们介绍使用它来与一些核心Spring功能集成的不同方式,以及如何利用它的许多好处。

2. Spring ApplicationContext

在我们讲解@Component的之前, 我们首先需要对Spring ApplicationContext有一定的了解。

ApplicationContext是Spring保存已识别为自动管理和分发的对象实例的地方,这些被称为Bean。

Bean管理和依赖注入是Spring的一些主要特性。

使用控制反转原理,Spring从我们的应用程序中收集bean实例,并在适当的时候使用它们。 我们可以向Spring提供bean依赖关系,而无需处理这些对象的设置和实例化。

使用@Autowired等注解将Spring管理的bean注入我们的应用程序的能力是在Spring中创建强大且可扩展的代码的驱动力。

那么,我们如何告诉Spring我们希望它为我们管理的bean呢? 我们应该利用Spring的自动bean检测,在类上使用原型注解

3. @Component

@Component是一个注解,它允许Spring自动检测我们的自定义bean。

换句话说,无需编写任何显式代码,Spring将:

  • 扫描我们的应用程序,寻找带有@Component注解的类。
  • 实例化它们并将任何指定的依赖项注入其中。
  • 在需要的地方注入它们。

但是,大多数开发人员更喜欢使用更专用的构造型注解来提供此功能。

3.1 Spring原型注解

Spring提供了一些专门的原型注解:@Controller、@Service和@Repository。它们都提供与@Component相同的功能。

它们的行为都是一样的,因为它们都是以@Component作为每个注解的元注解的组合注解。 它们类似与@Component的别名,在Spring自动检测或依赖注入之外具有特殊用途和含义。

如果我们想,理论上我们可以选择专门使用@Component来满足我们的bean自动检测需求。 另一方面,我们也可以使用@Component编写自己的专用注解。

但是,Spring的其他功能专门寻找Spring的专用注解以提供额外的自动化优势。 因此,我们可能应该在大多数情况下坚持使用特定注解

假设我们在Spring Boot项目中有上述注解的示例:


@Controller
public class ControllerExample {

}

@Service
public class ServiceExample {

}

@Repository
public class RepositoryExample {

}

@Component
public class ComponentExample {

}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface CustomComponent {

}

@CustomComponent
public class CustomComponentExample {

}

我们可以编写一个测试,证明每一个都被Spring自动检测并添加到ApplicationContext中:


@SpringBootTest
@ExtendWith(SpringExtension.class)
class ComponentUnitTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void givenInScopeComponents_whenSearchingInApplicationContext_thenFindThem() {
        assertNotNull(applicationContext.getBean(ControllerExample.class));
        assertNotNull(applicationContext.getBean(ServiceExample.class));
        assertNotNull(applicationContext.getBean(RepositoryExample.class));
        assertNotNull(applicationContext.getBean(ComponentExample.class));
        assertNotNull(applicationContext.getBean(CustomComponentExample.class));
    }
}

3.2 @ComponentScan

在我们完全依赖@Component之前,我们必须明白它只是一个普通的注解。注解用于将bean与其他对象(例如域对象)区分开来。

然而,Spring使用@ComponentScan注解将它们全部收集到它的ApplicationContext中

如果我们正在编写一个Spring Boot应用程序,我们要知道@SpringBootApplication是一个包含@ComponentScan的组合注解。 只要我们的@SpringBootApplication类在我们项目的根包下,它就会默认扫描我们定义的每一个@Component。

但是,如果我们的@SpringBootApplication类不是位于项目的根包下,或者我们想要扫描外部源, 我们可以显式配置@ComponentScan以扫描我们指定的任何包,只要它存在于类路径中。

让我们定义一个超出范围的@Component bean:

package cn.tuyucheng.taketoday.component.scannedscope;

@Component
public class ScannedScopeExample {

}

接下来,我们可以显式将其包含到我们的@ComponentScan注解中:

package cn.tuyucheng.taketoday.component.inscope;

@SpringBootApplication
@ComponentScan({"cn.tuyucheng.taketoday.component.inscope", "cn.tuyucheng.taketoday.component.scannedscope"})
public class ComponentApplication {

    public static void main(String[] args) {
        SpringApplication.run(ComponentApplication.class, args);
    }
}

最后,我们可以测试它是否存在:


class ComponentUnitTest {

    @Test
    void givenScannedScopeComponent_whenSearchingInApplicationContext_thenFindIt() {
        assertNotNull(applicationContext.getBean(ScannedScopeExample.class));
    }
}

实际上,当我们想要扫描项目中包含的外部依赖项时,更有可能发生这种情况。

3.3 @Component的局限性

在某些情况下,当我们无法使用@Component时,我们希望某个对象成为Spring管理的bean。

让我们在项目外部的包中定义一个用@Component标注的对象:

package cn.tuyucheng.taketoday.component.outsidescope;

@Component
public class OutsideScopeExample {

}

下面是一个证明ApplicationContext不包含OutsideScopeExample bean的测试:


@SpringBootTest
@ExtendWith(SpringExtension.class)
class ComponentUnitTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void givenOutsideScopeComponent_whenSearchingInApplicationContext_thenFail() {
        assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(OutsideScopeExample.class));
    }
}

此外,我们可能无法访问第三方库的源代码,并且我们无法添加@Component注解。 或者,也许我们希望根据我们运行的环境有条件地使用一个bean实现而不是另一个实现。 大多数时候自动检测就足够了,但如果不满足,我们可以使用@Bean

4. @Component与@Bean

@Bean也是Spring在运行时用来收集bean的注解,但它不在类级别使用。 我们使用@Bean标注方法,以便Spring可以将方法的返回结果配置为Spring bean

我们首先创建一个不带任何注解的POJO:

public class BeanExample {

}

在用@Configuration标注的类中,我们可以创建一个生成bean的方法:


@Configuration
public class ComponentApplication {

    @Bean
    public BeanExample beanExample() {
        return new BeanExample();
    }
}

BeanExample可能是一个项目的本地类,也可能是一个外部类。没关系,因为我们只需要返回它的一个实例。

然后我们可以编写一个测试来验证Spring确实配置了bean:


@SpringBootTest
@ExtendWith(SpringExtension.class)
class ComponentUnitTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void givenBeanComponents_whenSearchingInApplicationContext_thenFindThem() {
        assertNotNull(applicationContext.getBean(BeanExample.class));
    }
}

由于@Component和@Bean之间的差异,我们应该注意一些重要的含义。

  • @Component是类级别的注解,而@Bean是方法级别的注解,所以@Component只有在源代码可以编辑的情况下可用。 而@Bean总是可以使用,但它更冗长。
  • @Component兼容Spring的自动检测,但@Bean需要手动实例化类。
  • 使用@Bean将bean的实例化与其类定义分离。这就是为什么我们可以使用它甚至将第三方类配置成Spring bean。 这也意味着我们可以引入逻辑来决定bean使用几个可能的实例选项中的哪一个。

5. 总结

我们介绍了Spring @Component注解以及其他相关方面。首先,我们讨论了各种Spring原型注解,它们只是@Component的特殊版本。

@Component本身不会做任何事情,除非它可以被@ComponentScan扫描到。

最后,由于不可能在不能修改源代码的类上使用@Component,我们只能使用@Bean注解。

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

Show Disqus Comments

Post Directory

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