Note 01. Spring 设计哲学与 IoC 容器一文带你理解spring框架的设计理念
Note 01. Spring 设计哲学与 IoC 容器一文带你理解spring框架的设计理念
ProriseNote 01. IoC 容器与依赖注入:Spring 的设计基石
摘要:为什么我们不能直接 new 对象?Spring 的 IoC 容器到底在帮我们做什么?本章将从一个 “坏代码” 的案例出发,逐步揭示控制反转(IoC)的设计哲学,深入理解依赖注入(DI)的三种方式及其设计权衡,并剖析 Spring 容器的内部结构。读完本章,你将建立起对 Spring 核心设计的完整心智模型。
本章学习路径
- 问题驱动:通过 “坏代码” 案例,理解传统对象创建方式的致命缺陷。
- 思想升级:掌握 IoC 的本质——一场对象创建责任的转移。
- 落地方式:深入理解 DI 的三种注入方式及 Spring 官方的推荐理由。
- 容器认知:区分 BeanFactory 与 ApplicationContext,理解容器的层级设计。
重要信息: 概念章节一共有五章,我们的起步是您至少对于 Springboot 已经有过了使用经验,我们的核心意图在于为您规划一个体系化的梳理路线,而不是纯零基础友好笔记
1.1. 从一个 “坏代码” 说起:为什么不能直接 new 对象
在正式进入 Spring 的世界之前,我们先来看一段 “看起来没问题” 的代码。这段代码在很多初学者的项目中随处可见,但它却隐藏着严重的设计缺陷。
1.1.1. 一个典型的订单服务
假设我们正在开发一个电商系统,需要实现一个订单服务。订单创建时,需要校验库存、计算价格、保存订单、发送通知。一个 “直觉式” 的实现可能是这样的:
1 | /** |
这段代码能正常运行,业务逻辑也很清晰。但当项目逐渐复杂,你会发现自己陷入了一个又一个的困境。
1.1.2. 困境一:无法进行单元测试
“我想测试 createOrder 方法的业务逻辑,但每次运行测试都会真的去查数据库、发邮件,怎么办?”
问题的根源在于:OrderService 内部直接 new 出了所有依赖。我们无法在测试时将 OrderRepository 替换成一个假的(Mock)实现。
1 | // 我们期望的测试方式 |
1.1.3. 困境二:修改需求导致代码 “牵一发动全身”
产品经理说:“我们要支持短信通知了,不只是邮件。”
现在你需要修改 OrderService 的代码:
1 | // 修改前 |
更糟糕的是,如果系统中有 50 个地方都 new EmailNotificationService(),你需要改 50 处。这直接违反了软件设计的 开闭原则(OCP):对扩展开放,对修改关闭。
1.1.4. 困境三:对象创建逻辑散落各处
随着系统演进,InventoryService 的构造变得复杂了——它需要一个数据库连接池、一个缓存客户端、一个配置对象:
1 | // InventoryService 的构造变复杂了 |
现在,所有 new InventoryService() 的地方都要改成:
1 | private InventoryService inventoryService = new InventoryService( |
对象的创建逻辑像病毒一样扩散到了业务代码的每个角落。
1.1.5. 问题的本质:职责错位
让我们用一张图来总结这段 “坏代码” 的问题:
OrderService 本应只关心 “如何处理订单” 这一件事,但现在它还要操心:
- 每个依赖对象怎么创建
- 每个依赖对象需要什么参数
- 应该选择哪个具体实现类
这就是 职责错位——业务类承担了本不属于它的 “对象创建” 职责。
1.2. 控制反转(IoC):一场责任的转移
上一节我们看到了直接 new 对象带来的种种问题。解决这些问题的核心思想,就是 控制反转(Inversion of Control,IoC)。
1.2.1. IoC 的本质:从 “主动索取” 到 “被动接收”
“控制反转” 这个名字听起来很抽象,但它的核心思想其实非常简单:
不要让业务类自己去创建依赖,而是让外部把依赖 “送” 进来。
我们来对比一下 “反转前” 和 “反转后” 的代码:
反转前:主动索取
1 | public class OrderService { |
反转后:被动接收
1 | public class OrderService { |
这个看似微小的改变,带来了质的飞跃:
| 对比维度 | 反转前(主动索取) | 反转后(被动接收) |
|---|---|---|
| 依赖关系 | 编译时确定,写死在代码里 | 运行时确定,可灵活替换 |
| 可测试性 | 无法 Mock,必须用真实依赖 | 轻松注入 Mock 对象 |
| 扩展性 | 换实现要改源码 | 换实现只需改配置 |
| 职责分离 | 业务类兼任 “对象工厂” | 业务类只关心业务 |
1.2.2. IoC 容器的角色:对象的创建者、管理者、协调者
既然业务类不再负责创建依赖,那 “谁” 来负责呢?答案是:IoC 容器。
Spring 的 IoC 容器扮演着三重角色:
角色一:对象创建者
容器负责实例化所有被管理的对象(在 Spring 中称为 Bean)。我们不再写 new OrderService(),而是向容器 “要” 一个 OrderService。
角色二:生命周期管理者
容器不仅创建对象,还管理对象的整个生命周期——初始化、使用、销毁。这让我们可以在对象生命周期的关键节点插入自定义逻辑(如资源初始化、连接池预热、资源释放等)。
角色三:依赖关系协调者
容器会分析所有对象之间的依赖关系,并按照正确的顺序创建和装配它们。比如,容器知道要先创建 OrderRepository,再创建 OrderService,然后把前者注入到后者中。
1.2.3. 设计模式视角:IoC 与工厂模式、服务定位器的对比
IoC 并不是凭空出现的,它与几种经典设计模式有着密切的关系。理解它们的异同,能帮助我们更深刻地把握 IoC 的设计精髓。
方案一:简单工厂模式
1 | public class ServiceFactory { |
工厂模式将对象创建逻辑集中到了工厂类中,解决了 “创建逻辑散落各处” 的问题。但 OrderService 仍然 主动调用 工厂来获取依赖,依赖关系仍然是硬编码的。
方案二:服务定位器模式
1 | public class ServiceLocator { |
服务定位器提供了一个全局的 “服务注册表”,对象可以从中查找自己需要的依赖。但问题在于:
OrderService仍然 主动查找 依赖- 依赖关系被隐藏在代码内部,从类的接口(构造函数、方法签名)上看不出来
- 难以进行单元测试(需要预先配置好 ServiceLocator)
方案三:IoC / 依赖注入
1 | public class OrderService { |
IoC 的革命性在于:对象完全不知道依赖从哪里来。它只是声明 “我需要一个 OrderRepository”,至于这个对象是怎么创建的、是真实的还是 Mock 的,它完全不关心。
三种方案的对比:
| 对比维度 | 简单工厂 | 服务定位器 | IoC/DI |
|---|---|---|---|
| 依赖获取方式 | 主动调用 | 主动查找 | 被动接收 |
| 依赖可见性 | 隐藏在实现中 | 隐藏在实现中 | 显式声明在接口上 |
| 可测试性 | 需要 Mock 工厂 | 需要配置定位器 | 直接传入 Mock |
| 代码侵入性 | 依赖工厂类 | 依赖定位器类 | 无任何依赖 |
1.3. 依赖注入(DI):IoC 思想的落地方式
IoC 是一种设计思想,而 依赖注入(Dependency Injection,DI) 是实现这种思想最主流的方式。但在深入讨论注入方式之前,我们需要先回答一个更基础的问题:Spring 到底在 “注入” 什么?
1.3.1. 前置知识:什么是 Bean?
在 Spring 的世界里,Bean 是一个被反复提及的核心概念。但很多初学者对它的理解仅停留在 “就是一个 Java 对象” 的层面。这种理解虽然不算错,但过于肤浅,无法帮助我们真正理解 Spring 的设计。
Bean 的精确定义
Bean 是 由 Spring IoC 容器创建、管理和装配的对象。
这个定义包含三个关键动作:
- 创建:对象的实例化由容器负责,而不是我们手动
new - 管理:对象的整个生命周期(从出生到死亡)由容器掌控
- 装配:对象之间的依赖关系由容器负责建立
Bean 与普通 Java 对象的本质区别
1 | // 这是一个普通的 Java 对象 |
表面上看,两者都是 User 类型的对象。但它们的 “身份” 完全不同:
| 对比维度 | 普通 Java 对象 | Spring Bean |
|---|---|---|
| 创建者 | 开发者手动 new | Spring 容器通过反射创建 |
| 生命周期 | 由 JVM 垃圾回收决定 | 由容器管理(可配置单例、原型等) |
| 依赖关系 | 手动建立 | 容器自动装配 |
| 增强能力 | 无 | 可被 AOP 代理、事务管理等增强 |
| 可见性 | 仅在创建它的作用域内可见 | 在整个容器范围内可被获取 |
一个形象的比喻
如果把 Spring 容器比作一个 “人才市场”,那么:
- 普通 Java 对象:就像你自己在街上随便找的临时工,你要自己面试、自己培训、自己管理,用完就散
- Spring Bean:就像通过正规人才市场招聘的员工,市场帮你筛选、培训、建立档案,你只需要说 “我要一个会 Java 的”,市场就给你送来一个合格的
什么样的对象应该成为 Bean?
并不是所有对象都适合交给 Spring 管理。一般来说,以下类型的对象适合成为 Bean:
简单的判断原则:
- ✅ 应该成为 Bean:需要被多处复用、需要依赖其他组件、需要被容器管理生命周期的对象
- ❌ 不应该成为 Bean:纯粹承载数据的对象(如实体类、DTO)、无状态的工具类
1.3.2. 依赖注入的本质:容器如何 “送货上门”
理解了 Bean 的概念后,我们再来看依赖注入就清晰多了。
依赖注入的定义
依赖注入是指:容器在创建 Bean 的过程中,自动将该 Bean 所依赖的其他 Bean “注入” 到它内部。
用更通俗的话说:你只需要告诉容器 “我需要什么”,容器就会把你需要的东西 “送货上门”。
从 “自己去拿” 到 “送货上门” 的转变
1 | // ❌ 传统方式:自己去 "拿" 依赖 |
这个转变看似简单,但它彻底改变了对象之间的协作方式:
1.3.3. 三种注入方式的演进与设计权衡
Spring 支持三种依赖注入方式,它们的出现有其历史背景,各有适用场景。
方式一:Setter 注入(Spring 早期的主流方式)
历史背景
在 Spring 1.x 和 2.x 时代,Setter 注入是最主流的方式。这与当时的技术背景有关:
- 早期 Spring 主要使用 XML 配置,XML 中配置
<property>标签来调用 setter 方法非常直观 - JavaBean 规范深入人心,getter/setter 是标准的属性访问方式
- 当时的 IDE 对构造器参数的支持不如现在完善
XML 时代的 Setter 注入配置
1 | <!-- Spring 早期的典型配置方式 --> |
对应的 Java 代码
1 | public class OrderService { |
注解时代的 Setter 注入
随着 Spring 2.5 引入注解支持,Setter 注入也可以用注解方式:
1 |
|
Setter 注入的特点分析
| 优点 | 缺点 |
|---|---|
| 可以注入可选依赖(依赖可以为 null) | 无法保证依赖的不可变性(没有 final) |
| 可以在对象创建后重新注入(灵活) | 对象可能处于 “部分初始化” 状态 |
| XML 配置时语义清晰 | 依赖关系分散在多个 setter 中,不够直观 |
| 可以解决循环依赖问题 | 容易遗漏必需的依赖 |
现代开发中的定位
在现代 Spring 开发中,Setter 注入主要用于 可选依赖 的场景:
1 |
|
方式二:字段注入(最简洁但不推荐)
出现背景
字段注入随着 Spring 2.5 的 @Autowired 注解一起出现。它的写法极其简洁,因此迅速流行起来,尤其在快速开发和教程示例中被大量使用。
代码示例
1 |
|
为什么字段注入如此流行?
- 代码最少:不需要写构造器,不需要写 setter,一个注解搞定
- 添加依赖成本低:需要新依赖?加一行
@Autowired private XxxService xxx;就行 - 教程友好:示例代码简短,便于展示核心逻辑
为什么 Spring 官方不推荐字段注入?
尽管字段注入写起来很爽,但它有几个严重的设计缺陷:
缺陷一:无法使用 final,破坏不可变性
1 |
|
这涉及 Java 语言的基础知识:
final字段必须在 对象构造完成之前 被赋值- 字段注入是在对象构造 完成之后,通过反射强行赋值的
- 因此,字段注入与
final在语义上是冲突的
缺陷二:依赖关系被隐藏,违反显式依赖原则
1 | // 从类的公开接口(构造器、方法签名)完全看不出它依赖什么 |
缺陷三:单元测试困难
1 | // 如何在不启动 Spring 容器的情况下测试 OrderService? |
对比构造器注入的测试:
1 |
|
缺陷四:容易导致类职责膨胀
字段注入的 “低成本” 反而成了一个陷阱:
1 | // 加一个依赖太容易了,不知不觉就变成了这样 |
方式三:构造器注入(现代 Spring 的推荐方式)
历史演进
构造器注入并不是新概念,Spring 从 1.x 版本就支持。但在早期,由于 XML 配置构造器参数比较繁琐,它不如 Setter 注入流行。
1 | <!-- 早期 XML 配置构造器注入,需要指定参数顺序或名称 --> |
随着注解配置的普及,特别是 Spring 4.3 引入的 “单构造器自动注入” 特性,构造器注入变得非常简洁:
1 |
|
配合 Lombok 进一步简化
在实际项目中,我们通常配合 Lombok 的 @RequiredArgsConstructor 注解,让代码更加简洁:
1 |
|
1.3.4. 三种注入方式的全面对比
现在我们已经详细了解了三种注入方式,让我们做一个全面的对比:
| 对比维度 | 构造器注入 | Setter 注入 | 字段注入 |
|---|---|---|---|
| Spring 官方态度 | ⭐⭐⭐⭐⭐ 强烈推荐 | ⭐⭐⭐ 可选依赖时使用 | ⭐⭐ 不推荐 |
| 不可变性(final) | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
| 依赖可见性 | ✅ 构造器签名即依赖清单 | ⚠️ 分散在多个方法 | ❌ 隐藏在类内部 |
| 空指针安全 | ✅ 创建时即完整 | ❌ 可能部分初始化 | ❌ 可能为 null |
| 单元测试 | ✅ 直接 new 传参 | ⚠️ 需调用多个 setter | ❌ 需要反射或容器 |
| 循环依赖 | ❌ 无法解决 | ✅ 可解决 | ✅ 可解决 |
| 代码量 | ⚠️ 参数多时较长 | ⚠️ 方法多时较长 | ✅ 最少 |
| IDE 支持 | ✅ 重构友好 | ✅ 重构友好 | ⚠️ 反射注入,重构需谨慎 |
1.3.5. @Autowired 的依赖查找机制
当我们使用 @Autowired 注解时,Spring 容器需要找到正确的 Bean 来注入。这个查找过程遵循一套明确的规则。
第一步:按类型查找(byType)
Spring 首先会在容器中查找与目标类型匹配的 Bean:
1 |
|
第二步:处理多个候选者
如果容器中存在多个相同类型的 Bean,Spring 会进入更精细的筛选流程:
1 | // 假设容器中有两个 MessageSender 类型的 Bean |
解决多候选者的三种方式
方式一:利用字段名匹配 Bean 名称
1 |
|
方式二:使用 @Qualifier 显式指定
1 |
|
方式三:使用 @Primary 标记默认 Bean
1 |
|
完整的查找流程图
构造器注入中的 @Qualifier 使用
在构造器注入中,@Qualifier 需要放在参数上:
1 |
|
1.4. Spring 容器的两张面孔:BeanFactory 与 ApplicationContext
在前面的章节中,我们多次提到 “Spring 容器” 这个概念。现在是时候深入了解它的内部结构了。Spring 提供了两个核心的容器接口:BeanFactory 和 ApplicationContext。理解它们的关系和区别,是深入理解 Spring 架构的关键。
1.4.1. 为什么需要 “容器”?从手工作坊到自动化工厂
在理解 Spring 容器之前,让我们先思考一个问题:如果没有容器,我们如何管理对象?
场景:一个中型电商系统的对象创建
假设我们有一个订单模块,涉及以下组件:
1 | OrderController |
没有容器时的代码
1 | public class Application { |
这段代码的问题
- 创建顺序必须手动管理:必须先创建 DataSource,才能创建 Repository,才能创建 Service…
- 依赖关系硬编码:如果 OrderService 需要新增一个依赖,这里就要改
- 生命周期手动管理:DataSource 需要在应用关闭时释放连接,谁来负责?
- 无法灵活替换:想把 EmailSender 换成 MockEmailSender 做测试?改代码吧
- 代码膨胀:随着系统增长,这个 “组装代码” 会越来越长
容器的价值:自动化的对象管理
Spring 容器的核心价值就是 将上述所有工作自动化:
1 | // 有了 Spring 容器,启动代码变成这样 |
容器就像一个 自动化工厂,我们只需要告诉它 “有哪些零件”(通过注解或配置),它就能自动完成 “组装” 工作。
1.4.2. BeanFactory:IoC 容器的最小契约
BeanFactory 是 Spring IoC 容器的 根接口,它定义了容器最基本、最核心的功能。可以把它理解为容器的 “最低标准”——任何 Spring 容器都必须实现这些基本能力。
BeanFactory 接口定义
1 | public interface BeanFactory { |
BeanFactory 的设计哲学
BeanFactory 的设计遵循 最小化原则——它只定义了 “获取 Bean” 和 “查询 Bean 信息” 这两类最基本的操作,不包含任何 “高级” 功能。
这种设计有几个好处:
- 职责单一:BeanFactory 只负责 “Bean 的获取”,不掺杂其他功能
- 易于实现:第三方可以轻松实现一个最小化的 IoC 容器
- 资源友好:在资源受限的环境(如早期的移动设备)中,可以使用轻量级实现
BeanFactory 的核心特点
| 特点 | 说明 |
|---|---|
| 延迟加载(Lazy Loading) | Bean 在第一次被请求时才创建,而不是容器启动时就创建 |
| 最小功能集 | 只提供 Bean 的获取和查询,不支持 AOP、事件、国际化等 |
| 资源占用小 | 适合资源受限的环境 |
| 需要手动注册后置处理器 | BeanPostProcessor 等扩展点需要手动注册 |
BeanFactory 的使用示例(了解即可)
在现代 Spring 开发中,我们几乎不会直接使用 BeanFactory。但了解它的用法有助于理解容器的本质:
1 | // 早期 Spring 使用 BeanFactory 的方式(现已很少使用) |
1.4.3. ApplicationContext:企业级功能的全面扩展
ApplicationContext 是 BeanFactory 的子接口,它在 BeanFactory 的基础上扩展了大量企业级应用所需的功能。在早期的spring框架实际开发中,我们使用的几乎都是 ApplicationContext。
ApplicationContext 的接口继承体系
1 | public interface ApplicationContext extends |
ApplicationContext 相比 BeanFactory 的增强功能
| 功能领域 | BeanFactory | ApplicationContext | 说明 |
|---|---|---|---|
| Bean 获取 | ✅ | ✅ | 基础能力,两者都支持 |
| Bean 生命周期管理 | ✅ | ✅ | 基础能力,两者都支持 |
| 单例 Bean 预加载 | ❌ 懒加载 | ✅ 启动时预加载 | ApplicationContext 在启动时就创建所有单例 Bean |
| BeanPostProcessor 自动注册 | ❌ 需手动 | ✅ 自动发现并注册 | 简化了扩展点的使用 |
| 国际化(i18n) | ❌ | ✅ MessageSource | 支持多语言消息 |
| 事件发布机制 | ❌ | ✅ ApplicationEventPublisher | 支持应用内事件的发布与监听 |
| AOP 支持 | ❌ | ✅ | 自动代理、切面织入 |
| 资源加载 | ❌ | ✅ ResourceLoader | 统一的资源访问抽象 |
| 环境抽象 | ❌ | ✅ Environment | Profile、Properties 管理 |
| 父子容器 | ❌ | ✅ HierarchicalBeanFactory | 支持容器层级结构 |
预加载 vs 懒加载的区别
这是 BeanFactory 和 ApplicationContext 最显著的行为差异:
1 | // BeanFactory:懒加载 |
预加载的好处:
- 快速失败:如果 Bean 配置有问题(如循环依赖、缺少依赖),在启动时就会报错,而不是运行时
- 启动后响应快:所有 Bean 都已就绪,第一次请求不会有创建延迟
- 资源预热:数据库连接池、缓存等可以在启动时就初始化好
常用的 ApplicationContext 实现类
Spring 提供了多种 ApplicationContext 实现,适用于不同的场景:
各实现类的适用场景
| 实现类 | 配置方式 | 适用环境 | 使用场景 |
|---|---|---|---|
AnnotationConfigApplicationContext | 注解/JavaConfig | 非 Web | 单元测试、独立应用、批处理任务 |
ClassPathXmlApplicationContext | XML | 非 Web | 遗留项目、需要 XML 配置的场景 |
AnnotationConfigServletWebServerApplicationContext | 注解/JavaConfig | Servlet Web | Spring Boot Web 应用(默认) |
AnnotationConfigReactiveWebServerApplicationContext | 注解/JavaConfig | 响应式 Web | Spring WebFlux 应用 |
Spring Boot 中的容器
在 Spring Boot 应用中,我们通常不需要手动创建 ApplicationContext。SpringApplication.run() 会根据类路径自动选择合适的实现:
1 |
|
1.4.4. 容器的层级结构与父子容器
Spring 支持容器的层级结构——一个容器可以有一个父容器。这种设计在某些场景下非常有用。
父子容器的核心规则
- 子容器可以访问父容器中的 Bean
- 父容器无法访问子容器中的 Bean
- 子容器中的 Bean 可以覆盖父容器中同名的 Bean
父子容器的典型应用:传统 Spring MVC
在传统的 Spring MVC 应用中(非 Spring Boot),通常会配置两个容器:
web.xml 配置示例
1 | <web-app> |
两个容器的职责分工
| 容器 | 创建者 | 存放的 Bean | 配置文件 |
|---|---|---|---|
| Root WebApplicationContext(父) | ContextLoaderListener | Service、Repository、DataSource 等业务组件 | applicationContext.xml |
| Servlet WebApplicationContext(子) | DispatcherServlet | Controller、ViewResolver、HandlerMapping 等 Web 组件 | dispatcher-servlet.xml |
这种设计的好处
- 职责分离:Web 层组件和业务层组件分开管理,边界清晰
- 复用性:一个父容器可以被多个子容器共享(多个 DispatcherServlet 场景)
- 隔离性:不同的 DispatcherServlet 可以有不同的 Web 配置,但共享业务组件
Bean 查找规则详解
当从子容器获取 Bean 时,查找顺序是:
代码示例
1 | // 假设 UserService 定义在父容器中 |
Spring Boot 的简化
在 Spring Boot 中,默认只有一个 ApplicationContext,不再区分父子容器:
1 |
|
这种简化带来的好处:
- 配置更简单:不需要维护多个配置文件
- 理解成本低:不需要考虑 Bean 在哪个容器中
- 调试更容易:所有 Bean 都在一个地方
但也有一些场景仍然需要父子容器的设计,比如:
- 需要在一个应用中部署多个相互隔离的模块
- 需要实现插件化架构,插件有自己独立的容器
1.5. 本章总结与核心概念速查
摘要回顾
本章我们从一个 “坏代码” 案例出发,揭示了直接 new 对象带来的三大困境:无法测试、难以扩展、职责混乱。为了解决这些问题,我们引入了 控制反转(IoC) 的设计思想——将对象创建的责任从业务类转移到外部容器。
依赖注入(DI) 是 IoC 最主流的实现方式。我们深入分析了三种注入方式的演进历史和设计权衡:
- Setter 注入:Spring 早期的主流方式,适合可选依赖
- 字段注入:最简洁但不推荐,无法使用 final,测试困难
- 构造器注入:现代 Spring 的推荐方式,支持不可变性,依赖显式化
最后,我们剖析了 Spring 容器的两个核心接口:
- BeanFactory:最小化的 IoC 容器契约,定义了获取 Bean 的基本能力
- ApplicationContext:企业级功能的全面扩展,是我们日常使用的容器
核心概念速查表
| 概念 | 定义 | 关键要点 |
|---|---|---|
| Bean | 由 Spring 容器创建、管理和装配的对象 | 不是所有对象都应该成为 Bean,只有需要被容器管理的组件才应该 |
| IoC(控制反转) | 将对象创建和依赖管理的控制权从业务代码转移到外部容器 | 核心是 “责任转移”,业务类不再负责创建依赖 |
| DI(依赖注入) | IoC 的实现方式,由容器将依赖 “推送” 给对象 | 对象被动接收依赖,无需主动获取 |
| 构造器注入 | 通过构造函数参数注入依赖 | ⭐ 官方推荐,支持 final,依赖显式,便于测试 |
| Setter 注入 | 通过 setter 方法注入依赖 | 适合可选依赖,可解决循环依赖 |
| 字段注入 | 通过反射直接注入字段 | ❌ 不推荐,无法 final,测试困难,依赖隐藏 |
| BeanFactory | Spring IoC 容器的根接口 | 最小化契约,懒加载,功能有限 |
| ApplicationContext | BeanFactory 的增强版 | 企业级功能,预加载单例,支持 AOP/事件/国际化 |
| @Autowired | 自动装配注解 | 按类型查找 → 按名称匹配 → @Qualifier 指定 |
| @Qualifier | 指定注入的 Bean 名称 | 解决多个同类型 Bean 的歧义问题 |
| @Primary | 标记首选 Bean | 多个同类型 Bean 时,优先注入被标记的 |
本章核心心智模型
从 “手工作坊” 到 “自动化工厂” 的转变
核心认知升级
读完本章,你应该建立起以下认知:
- IoC 不是语法糖:它是一种架构级的责任重新分配,让业务类专注于业务逻辑
- Bean 不只是 “对象”:它是被容器管理的、有生命周期的、可被增强的特殊对象
- 构造器注入的 “冗长” 是特性:它让依赖关系显式化,让设计问题提前暴露
- ApplicationContext 是日常使用的容器:BeanFactory 是底层抽象,理解它有助于理解原理
- Spring Boot 简化了容器的使用:但底层原理没有变,理解原理才能解决复杂问题





















