人人都会AI编程

8.2 组件注册核心注解

更新时间:2026-07-10

Spring 容器的本质是一个管理对象(Bean)的装配工厂。要让容器接管某个类的实例化与生命周期,首先得告诉容器“哪些类需要被你管”。这就是组件注册。Spring 提供了一套层次分明的注解来完成这件事:从类级别的模式注解,到方法级别的 @Bean,再到按条件精细控制的 @Conditional,共同构成了一张灵活的注册网。

8.2.1 模式注解:把类直接交给容器

最常用的注册方式是在类上标注 @Component 及其衍生注解,让容器通过类路径扫描自动发现并加载。

| 注解 | 语义 | 适用场景 |
|------|------|----------|
| @Component | 通用组件 | 无法归类到更具体注解的工具类 |
| @Controller | 控制器组件 | Spring MVC 的控制器层 |
| @Service | 业务逻辑组件 | Service 层,无额外行为但增强可读性 |
| @Repository | 数据访问组件 | DAO 层,额外提供持久层异常翻译 |

从技术角度看,@Service@Controller@Repository 在源码层面都被 @Component 元注解标注,本质上是完全等价的。那为什么还要区分?为了语义清晰和框架增强。例如:

  • @Repository 会被 Spring 自动挂接持久层异常转换器,将数据库相关异常封装为 DataAccessException
  • @Controller 会被 Spring MVC 识别为处理 HTTP 请求的处理器,支持请求映射等功能。
  • 在 AOP 切点表达式中,也可以按注解类型进行精确拦截,比如 @within(org.springframework.stereotype.Service) 拦截所有 Service 层方法。

使用示例

@Service
public class OrderService {
    private final OrderRepository repository;

    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }
    // ...
}

@Repository
public class OrderRepository {
    // ...
}

默认情况下,这些注解不会自动生效,必须配合 @ComponentScan 或在 Spring Boot 应用中通过 @SpringBootApplication 间接启用扫描。扫描范围默认是该注解所在类的包及其子包。如果需要精确控制,可以在 @ComponentScan 中指定 basePackages 或使用 includeFiltersexcludeFilters 过滤特定类。

8.2.2 @Configuration 与 @Bean:方法级别的组件声明

当需要将第三方库中的类注册为 Bean,或一个 Bean 的创建过程包含复杂逻辑(如参数设置、条件判断)时,类级别的注解就显得力不从心。这时需要用到 Java 配置:在 @Configuration 标注的类中,通过 @Bean 方法显式创建对象。

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/business");
        config.setUsername("app");
        config.setPassword("secret");
        return new HikariDataSource(config);
    }
}

这里有一个容易被忽视却非常重要的细节:@Configuration 类默认使用 CGLIB 代理(proxyBeanMethods = true)。这意味着容器内的 @Bean 方法调用会被拦截,即便在同一个配置类中多次调用 dataSource() 方法,始终返回的是同一个单例 Bean。如果需要每次调用都创建一个新实例(轻量模式),可以设置 proxyBeanMethods = false,此时配置类不会被代理,多个 @Bean 方法之间直接调用会执行原生 Java 语义,每次 new 一个新对象。

对比两种注册方式:

  • @Component + 组件扫描:被动发现,类自己声明自己是一个组件。
  • @Configuration + @Bean主动声明,由开发者精确控制对象的创建步骤。

实际开发中二者常常配合使用:自己写的业务类使用 @Service@Repository 等模式注解;引入的外部组件或需要定制化初始化的对象则用 @Bean 注册。

8.2.3 @Import:快速导入配置或组件

当需要将另一个配置类或普通组件引入当前容器,而不想让它依赖于组件扫描时,可以使用 @Import。它就像一个“快速通道”,直接告诉容器“也把那个类加载进来”。

常见用法包括:

  • 引入普通配置类@Import(DataSourceConfig.class),让该类中的 @Bean 定义生效。
  • 引入 ImportSelector 实现:常用于框架内部的自动配置模块,根据条件动态返回要导入的类名数组。Spring Boot 的自动配置大量依赖此机制。
  • 引入 ImportBeanDefinitionRegistrar 实现:可在运行时通过编程方式注册 Bean,比 ImportSelector 拥有更高的灵活性。

一个直观的例子,当需要启用某个模块(如定时任务)时,通常只需一个注解:

@EnableScheduling
@Configuration
public class AppConfig {
}

@EnableScheduling 底层正是通过 @Import 导入了 SchedulingConfiguration 类,该配置类里又定义了一个 @Bean 方法注册了后置处理器。这种“注解驱动”的模式是 Spring 模块化集成的精妙设计。

8.2.4 条件化注册:按环境动态决定

真实项目中,并不是所有组件都要无条件创建。测试环境需要内存数据源,生产环境需要连接池——条件化注册让容器拥有了“判断力”。

Spring 提供了 @Conditional 注解,配合实现 Condition 接口的类,可在 Bean 注册前执行自定义匹配逻辑。Spring Boot 在此基础上,封装了一系列更易用的组合注解:

  • @ConditionalOnClass:类路径中存在指定类时才注册。
  • @ConditionalOnMissingBean:容器中不存在指定类型的 Bean 时才注册。
  • @ConditionalOnProperty:当配置文件中的某个属性满足指定条件时注册。
  • @ConditionalOnExpression:基于 SpEL 表达式的结果决定。

实际案例——只在开发环境启用一个用于调试的 Bean:

@Bean
@Profile("dev")
public DataSource devDataSource() {
    return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
}

@Profile 本身就是一个 @Conditional 的扩展,它让同一套代码能够适配开发、测试、生产等不同环境,避免编写多份配置文件。

8.2.5 影响 Bean 定义的其他注解

除了注册本身,还有一些注解直接影响 Bean 的行为和生命周期,它们也属于组件注册体系的一部分:

  • @Lazy:延迟初始化,容器启动时不会立即创建该 Bean,而是等到第一次被注入或获取时才实例化,适合启动耗时长或不一定会用到的组件。
  • @Scope:指定 Bean 的作用域,如 singleton(单例,默认)、prototype(每次获取都创建新实例)、requestsession 等。在 Web 开发中,@Scope("request") 可以将 Bean 的生命周期绑定到 HTTP 请求。
  • @Primary:当有多个同类型的候选 Bean 时,标记为 @Primary 的 Bean 会优先被注入,避免因歧义导致启动失败。
  • @DependsOn:强制指定当前 Bean 依赖于另一个 Bean 先初始化,尽管它们之间可能没有直接的字段注入关系。

这些注解往往配合 @Component@Bean 使用,让容器对 Bean 的控制更加精细。

8.2.6 注册方式的选择策略

面对多种注册手段,实际开发中可以遵循一个简单的决策路径:

  1. 自己编写的业务类(Controller、Service、Repository)→ 使用模式注解 @Controller@Service@Repository,让组件扫描自动发现。
  2. 第三方类或需要复杂构造逻辑的对象(数据源、RestTemplate、线程池)→ 在 @Configuration 类中用 @Bean 方法注册。
  3. 需要根据环境开关的组件→ 组合使用 @Profile@ConditionalOnProperty 等条件注解。
  4. 框架集成或启用模块→ 查找是否已有现成的 @EnableXxx 注解,它内部已经封装了 @Import 逻辑。

遵循这些原则,可以在不牺牲灵活性的前提下,保持组件注册的清晰与可控。容器拥有了明确的“势力范围”,后续的依赖注入、AOP 增强、事务管理等才能准确作用到每一个目标 Bean 上。