Skip to content

Spring IoC 容器

IoC 容器负责管理应用里的长期组件。它记录哪些类可以成为 Bean,决定这些 Bean 如何创建,负责把 Bean 之间的依赖连接起来,并在初始化和销毁阶段提供扩展点。

如果把业务应用看成一座工厂,IoC 容器不是生产线上的某个工人,而是设备台账和装配系统:哪些设备需要登记,设备之间怎么连接,启动前要做什么检查,关闭时要按什么顺序收尾,都由它统一管理。

Spring Bean 生命周期

详细位置

  • 本页说明 IoC 的概念边界、Bean 来源、依赖注入、生命周期和排错方式。
  • Spring 各模块关系见 Spring 总览
  • 事务和代理问题见 AOP
  • Web 请求中的 Controller、Service 装配见 Spring MVC

概念边界

概念准确含义直观理解
IoC对象创建和依赖组装的控制权从业务代码转移到容器对象不再自己找零件,而是由装配系统配齐
DI容器把一个对象需要的依赖传给它组件声明插口,容器插上线
Bean被 Spring 容器管理的对象登记在容器台账里的组件
ApplicationContext常用 Spring 容器应用启动后的组件仓库和运行上下文
BeanDefinitionBean 的元信息容器创建 Bean 前看的说明书

IoC 的重点不是让代码变短,而是让依赖关系显式、可替换、可检查。一个对象需要什么依赖,应能从构造器或配置中直接看出来,而不是藏在方法内部的 new 里。

哪些对象适合成为 Bean

适合注册为 Bean 的对象:

  • 应用服务:例如订单服务、支付服务、导出服务。
  • 数据访问对象:例如 Repository、Mapper、DAO。
  • 外部系统客户端:例如支付网关客户端、短信客户端、对象存储客户端。
  • 框架扩展点:例如拦截器、监听器、定时任务、消息消费者。
  • 配置对象:例如线程池、序列化器、业务配置绑定对象。

不适合注册为 Bean 的对象:

  • Entity、DTO、VO、Command、Request、Response。
  • 一次循环、一次请求、一次计算中临时创建的数据对象。
  • 持有用户临时状态或请求中间状态的对象。

判断标准是生命周期。Bean 通常像“设备”,在应用启动后长期存在;DTO 和 Entity 更像“单据”,在一次流程中流转,用完就结束。

Bean 的来源

Bean 主要来自两类入口。

组件扫描

应用内部自己写的组件,通常通过 @Service@Repository@Controller@Component 注册。它表达的是“这个类由容器管理”。

使用时应让注解表达分层语义:

注解更适合放在说明
@Controller / @RestControllerWeb 入口处理 HTTP 协议
@Service应用服务编排业务流程、控制事务边界
@Repository数据访问数据库、缓存、外部存储访问
@Component通用组件没有明确分层语义的工具型组件

配置类

第三方对象、带构造参数的 SDK Client、线程池、连接池等,通常通过 @Configuration@Bean 注册。它表达的是“这个对象怎样创建”。

组件扫描适合“类本身就是组件”;配置类适合“对象创建过程需要额外说明”。把复杂创建逻辑塞进业务类,会让业务代码同时承担装配职责。

依赖注入

依赖注入的目标是让对象只声明“我需要什么”,不关心“依赖从哪里来”。最推荐的是构造器注入,因为它有几个清晰特征:

  • 对象创建完成时,必需依赖已经齐全。
  • 依赖关系可以从构造器直接看见。
  • 字段可以保持不可变。
  • 单元测试可以直接传入替身对象。
  • 循环依赖会更早暴露。

Setter 注入适合可选依赖或运行期可替换依赖。字段注入虽然短,但依赖不透明,测试不方便,业务代码中不建议作为默认选择。

多实现如何选择

一个接口有多个实现时,容器会遇到“同一个插口有多根线”的问题。常见处理方式:

场景方式含义
调用方明确需要某个实现@Qualifier指定 Bean 名称或限定符
某个实现是默认选择@Primary多实现中优先使用它
调用方需要所有实现注入 List<T>Map<String, T>适合插件、策略、责任链

不要靠类名巧合或加载顺序解决多实现问题。多实现本身是设计信息,应该用注解或集合注入表达清楚。

生命周期

单例 Bean 的生命周期可以按这条线理解:

  1. 容器读取 Bean 定义。
  2. 调用构造器创建对象。
  3. 注入依赖和配置属性。
  4. 执行初始化前扩展。
  5. 执行初始化回调。
  6. 执行初始化后扩展。
  7. Bean 进入可用状态。
  8. 容器关闭时执行销毁回调。

初始化回调适合做必须在启动前完成的校验,例如配置完整性检查、本地缓存结构初始化。不适合放置耗时且非关键的远程调用,否则应用启动会被外部系统拖慢。

作用域

多数业务 Bean 使用默认的 singleton。它表示容器中只有一个实例,并不表示它必须持有全局状态。

单例 Bean 应尽量无状态。用户 ID、请求参数、导入进度、临时计算结果等不应放进单例字段,否则并发请求之间可能互相污染。

常见作用域:

作用域生命周期适用性
singleton容器内单例Service、Repository、Client、配置类
prototype每次获取创建新实例有状态任务对象,使用较少
request每个 HTTP 请求一个实例请求上下文
session每个 HTTP Session 一个实例会话状态,谨慎使用

配置绑定

零散的 @Value 适合少量简单值。只要配置形成一组,例如支付网关地址、Token、超时时间、重试次数,就更适合使用 @ConfigurationProperties

结构化配置的好处:

  • 配置项属于同一个对象,含义集中。
  • 可以设置默认值和校验规则。
  • 测试时可以直接构造配置对象。
  • 排查时更容易确认前缀、字段和最终值。

配置对象像“设备参数表”,业务组件只读取参数表,不应该到处散落读取配置文件。

常见问题

找不到 Bean

优先检查:

  1. 类是否在启动类所在包或子包下。
  2. 类是否通过组件注解或配置类注册。
  3. 条件装配是否没有命中。
  4. 测试环境是否缺少配置类。
  5. 依赖是否进入运行时 classpath。

Bean 超过一个

说明容器找到了多个候选对象,但注入点没有说明要哪个。用 @Qualifier 指定、用 @Primary 设置默认,或者改成注入集合。

循环依赖

循环依赖通常不是容器问题,而是服务边界问题。A 调 B,B 又调 A,往往说明两个服务职责互相缠绕。

优先处理方式:

  • 抽出共同依赖。
  • 调整服务边界。
  • 用事件解耦后续动作。
  • 把事务入口放到更清晰的应用服务层。

配置没有注入

检查顺序:

  1. 配置文件是否进入 classpath。
  2. 当前 profile 是否正确。
  3. 环境变量或命令行参数是否覆盖文件配置。
  4. 配置前缀和字段名是否匹配。
  5. 配置属性类是否被启用或扫描。

测试判断

业务单元测试不一定需要启动 Spring。构造器注入设计良好的类,可以直接传入替身依赖测试业务规则。只有需要验证组件扫描、条件装配、配置绑定、事务代理等框架行为时,才需要启动 Spring 测试上下文。

总结

IoC 的核心是明确对象管理边界。长期组件交给容器,临时数据留在业务流程里;必需依赖用构造器表达,多实现用明确规则选择;单例 Bean 保持无状态;排错时按扫描范围、注册来源、条件装配、多实现冲突、配置绑定逐层检查。

别急,先让缓存热一下。