1. 概述

在这篇文章中,我们将深入探讨 Spring 中的 @Component 注解及其相关概念。我们将了解如何通过它与 Spring 核心功能集成,并充分利用其带来的便利。

2. Spring ApplicationContext 简介

在深入理解 @Component 的作用之前,我们先简单了解一下 Spring ApplicationContext

Spring 的 ApplicationContext 是 Spring 容器中管理所有被标记为“受管 Bean”的地方。这些 Bean 是 Spring 自动创建并注入依赖的对象实例。

Spring 的核心功能之一就是 Bean 管理和依赖注入(DI)。

借助 控制反转(IoC) 原则,Spring 会自动收集我们应用中的 Bean 实例,并在需要时进行注入。我们可以告诉 Spring 这些 Bean 之间的依赖关系,而无需手动去创建它们。

使用如 @Autowired 这样的注解来自动注入 Spring 管理的 Bean,是构建强大、可扩展 Spring 应用的关键手段。

那么问题来了:我们如何告诉 Spring 哪些类是我们希望它来管理的呢?✅ 答案就是使用 Spring 提供的 stereotype 注解,比如 @Component 及其衍生注解。

3. @Component 注解详解

@Component 是一个用于标记类的注解,可以让 Spring 自动检测并管理该类的实例。

换句话说,我们不需要写任何显式的配置代码,Spring 就会:

  • 扫描所有带有 @Component 注解的类
  • 实例化这些类,并注入它们所依赖的其他 Bean
  • 在需要的地方自动注入这些 Bean

不过,大多数开发者更倾向于使用更具语义的衍生注解,比如 @Service@Controller@Repository

3.1. Spring Stereotype 注解

Spring 提供了几个专门用途的 stereotype 注解:

  • @Controller
  • @Service
  • @Repository

它们本质上和 @Component 是一样的,因为它们都以 @Component 作为元注解(meta-annotation)。换句话说,它们只是 @Component 的“别名”,但在语义上更明确,有助于代码可读性和框架层面的特殊处理。

✅ 我们完全可以只用 @Component 来做 Bean 的自动检测。反过来,我们也可以 自定义注解,只要加上 @Component 元注解即可。

不过,Spring 框架的其他模块(如 Web、Data JPA 等)会根据这些专门的注解提供额外的功能(如事务管理、异常转换等),所以 ❌ 一般建议还是使用这些专门的注解

来看个例子,我们定义了几个使用不同 stereotype 注解的类:

@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 正确识别并注入:

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

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public 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 是一个注解,但它本身不会触发 Spring 的自动扫描行为。✅ 真正起作用的是 @ComponentScan 注解,它告诉 Spring 去哪些包里扫描带有 @Component 的类。

在 Spring Boot 项目中,如果你使用了 @SpringBootApplication 注解,那它已经包含了 @ComponentScan,默认会扫描主类所在包及其子包下的所有 @Component 类。

但如果主类不在项目根目录,或者你想扫描外部包,就需要手动配置 @ComponentScan

举个例子,假设我们有一个类在另一个包中:

package com.baeldung.component.scannedscope;

@Component
public class ScannedScopeExample {
}

我们可以在主类上显式指定扫描路径:

package com.baeldung.component.inscope;

@SpringBootApplication
@ComponentScan({"com.baeldung.component.inscope", "com.baeldung.component.scannedscope"})
public class ComponentApplication {
    //public static void main(String[] args) {...}
}

然后测试一下是否成功注入:

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

⚠️ 这种场景在引入第三方库时比较常见,比如你需要把某个第三方类注册为 Spring Bean,但又不能修改它的源码。

3.3. @Component 的局限性

虽然 @Component 很强大,但它也有局限性。✅ **当一个类我们无法修改源码时,就无法使用 @Component**。

比如,我们定义一个类在项目外的包中:

package com.baeldung.component.outsidescope;

@Component
public class OutsideScopeExample {
}

测试一下,Spring 容器中是找不到它的:

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

另外,如果这个类来自第三方库,我们也没法给它加上 @Component。或者,我们想根据不同环境动态选择不同的实现类。这时候,自动扫描就不够用了。✅ 这时候,我们就需要用到 @Bean 注解

4. @Component vs @Bean

@Bean 也是 Spring 用来注册 Bean 的注解,但它不是用在类上的,而是用在方法上。

我们通过在方法上标注 @Bean,告诉 Spring 把这个方法的返回值注册为一个 Bean

我们先定义一个没有任何注解的 POJO:

public class BeanExample {
}

然后在配置类中使用 @Bean 注册它:

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

这个 BeanExample 可以是我们自己写的类,也可以是第三方库中的类。✅ 不管怎样,只要能返回一个实例即可。

我们写个测试验证它是否被成功注册:

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

⚠️ 两者之间有几个关键区别:

特性 @Component @Bean
作用位置 类级别 方法级别
是否需要源码
是否支持自动扫描 ✅ 是 ❌ 否
是否支持逻辑控制 ❌ 否 ✅ 是(可以加 if/else 等)

简单来说:

  • @Component 更简洁,但前提是你要能改类的源码。
  • @Bean 更灵活,适合处理第三方类或需要条件注入的场景。

5. 总结

这篇文章我们详细介绍了 Spring 的 @Component 注解及相关概念:

  • 首先介绍了各种 stereotype 注解,它们本质上都是 @Component 的别名。
  • 然后了解到 @Component 本身不会生效,需要配合 @ComponentScan
  • 最后,当自动扫描不适用时,我们可以通过 @Bean 来手动注册 Bean。

这些代码示例都可以在 GitHub 上找到。✅ 掌握这些内容,有助于你写出更清晰、更易维护的 Spring 应用。


原始标题:Spring @Component Annotation