北京时间 2026年4月9日 | 阅读约 10 分钟
在 Spring 生态中,控制反转(IoC) 与依赖注入(DI) 是任何一位开发者都绕不开的核心基石。面试时被问到“说一下你对 Spring 的理解”,十有八九要从它们讲起;工作中遇到的很多代码设计问题,归根结底也在它们身上。许多学习者的真实困境是:会配 @Autowired,却说不清 IoC 和 DI 究竟是什么关系;知道能解耦,却讲不出底层是怎么实现的。
这篇文章正是为了解决这个痛点。我们将从痛点切入 → 概念拆解 → 关系梳理 → 代码演示 → 底层原理 → 面试考点,一步步帮你建立起关于 Spring IoC 与 DI 的完整知识链路。
一、痛点切入:为什么需要 IoC 和 DI?
先看一段“传统写法”的代码:
public class UserController { private UserService userService; public UserController() { // 直接在构造方法中 new 出依赖对象 this.userService = new UserService(); } public void handleRequest() { userService.doBusiness(); } } public class UserService { private UserDao userDao; public UserService() { this.userDao = new UserDao(); } }
这段代码的问题在哪?
高耦合:
UserController与UserService的实现类绑死在一起,改不了也测不了。扩展性差:想替换成
UserServiceV2,得改UserController的源码。难以测试:想用 Mock 对象替代真实的
UserService做单元测试?做不到,因为对象在内部写死了。
这就是“控制正转”——对象主动去创建自己需要的依赖,自己掌握控制权。当系统规模变大、依赖关系变复杂时,这种方式会让代码变成一团乱麻。
为了解决这个问题,IoC 容器应运而生:把“创建对象”和“管理依赖”的权力从开发者手中转移到容器中。你只需要声明“我需要什么”,容器会在合适的时机把依赖给你送过来。
二、核心概念讲解:控制反转(IoC)
标准定义
IoC(Inversion of Control,控制反转) 是一种设计原则(programming principle),而非具体技术-。它的核心思想是:将对象的创建、配置和生命周期管理交给容器,而非由程序代码直接控制-。
拆解关键词
“控制” :指的是对象的创建权、依赖关系的管理权、生命周期的控制权。
“反转” :控制权从应用程序代码反转到外部容器。在传统模式下,A 需要 B 就自己
new B();在 IoC 模式下,A 不再自己创建 B,而是由容器把 B 送过来。
生活化类比:从“自由恋爱”到“父母之命”
想象一个场景:在现代社会,小明自由恋爱,自己找对象、自己追、自己处(对象由自己创建和控制)——这就是控制正转。有一天小明穿越到了古代,婚姻大事由父母和媒人全权操办,他只需要等着“被安排”(对象由外部容器管理)——这就是控制反转-31。
核心价值
IoC 让代码从“主动控制”变为“被动接收”,从而实现了组件间的解耦。对象之间不再直接持有强引用,耦合度显著降低,可测试性大幅提升-13。
三、关联概念讲解:依赖注入(DI)
标准定义
DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式。容器在创建 Bean 的过程中,自动将依赖的 Bean 注入到目标 Bean 中-15。用一句话概括:对象不自己找依赖,而是被动接收别人给它的依赖-4。
DI 的三种主流方式
1. 构造器注入(推荐)
@Service public class UserService { private final UserDao userDao; @Autowired public UserService(UserDao userDao) { this.userDao = userDao; // 依赖通过构造方法传入 } }
2. Setter 注入
@Service public class OrderService { private PaymentGateway gateway; @Autowired public void setGateway(PaymentGateway gateway) { this.gateway = gateway; // 依赖通过 setter 传入 } }
3. 字段注入(最常用但最不推荐)
@Service public class ProductService { @Autowired private InventoryService inventory; // 直接注入字段 }
为什么构造器注入被推荐?
| 维度 | 构造器注入 | 字段注入 |
|---|---|---|
| 不可变性 | ✅ 依赖可声明为 final | ❌ 无法声明 final |
| 空安全 | ✅ 对象创建时依赖必须就绪 | ❌ 可能出现依赖未注入的情况 |
| 测试友好 | ✅ 无需容器即可测试 | ❌ 依赖 Mock 框架或容器 |
| 循环依赖检测 | ✅ 启动时即可发现 | ❌ 可能运行时才发现 |
| 代码可读性 | ✅ 依赖一目了然 | ❌ 依赖藏在字段中 |
-3
四、概念关系与区别总结
一句话总结:IoC 是思想,DI 是手段。
IoC 是设计原则(What) :告诉你“控制权要反转出去”。
DI 是技术实现(How) :告诉你“如何把控制权反转出去——通过注入的方式”。
对比表格
| 维度 | IoC(控制反转) | DI(依赖注入) |
|---|---|---|
| 本质 | 设计思想 / 原则 | 具体实现 / 技术模式 |
| 关注点 | 谁控制谁 | 怎么把依赖送进去 |
| 可替代性 | 理论上可以用 DL(依赖查找)实现 | DI 是 Spring 中最常用的实现方式 |
| 层级 | 宏观 | 微观 |
-3-
💡 面试必备金句:IoC 是 Spring 框架的设计思想,DI 是 Spring 实现 IoC 的核心手段。两者相辅相成,共同构成了 Spring 容器的基础。
五、代码示例:从混乱到优雅
传统方式 vs Spring 方式对比
传统方式:硬编码依赖
public class OrderController { private OrderService orderService; public OrderController() { // 硬编码依赖,难以替换 this.orderService = new OrderServiceImpl(); } }
Spring 方式:声明式依赖
@RestController public class OrderController { @Autowired private OrderService orderService; // Spring 自动注入 @PostMapping("/order") public Result createOrder(@RequestBody OrderDTO order) { return orderService.create(order); } }
完整可运行示例:三层架构依赖注入
// 1. DAO 层 @Repository public class OrderDao { public void save(Order order) { System.out.println("订单已保存: " + order.getId()); } } // 2. Service 层 @Service public class OrderService { @Autowired private OrderDao orderDao; // Spring 自动注入 OrderDao public void createOrder(Order order) { // 业务逻辑处理... orderDao.save(order); } } // 3. Controller 层 @RestController public class OrderController { @Autowired private OrderService orderService; // Spring 自动注入 OrderService @PostMapping("/order") public String create(@RequestBody Order order) { orderService.createOrder(order); return "success"; } }
执行流程:Spring 容器启动时,扫描所有带有 @Component、@Service、@Repository、@RestController 注解的类,将它们封装为 BeanDefinition,然后依次实例化并完成依赖注入。最终,OrderController 拿到的是已经装配好 OrderService 的完整对象,OrderService 内部也已装配好 OrderDao-15。
@Autowired 工作流程
当 Spring 遇到 @Autowired 时,执行以下步骤:
根据需要的组件类型,到 IoC 容器中查找匹配的 Bean-23;
唯一匹配:直接注入,完成✔;
找不到匹配:装配失败,抛出异常❌;
找到多个匹配:进入冲突解决机制——若有
@Qualifier,按指定名称匹配;若没有,按成员变量名作为 Bean ID 进行匹配-23。
六、底层原理 / 技术支撑
Spring IoC 容器的底层实现,核心可以概括为四个字:工厂模式 + 反射。
1. 工厂模式
IoC 容器本质上就是一个超级对象工厂。BeanFactory 是 Spring 中最基础的 IoC 容器接口,定义了 getBean()、containsBean() 等核心方法。日常开发中我们通常使用它的增强版——ApplicationContext-15。
2. 反射机制
反射是 IoC 容器实现“运行时动态创建对象”的技术基础。Spring 在实例化 Bean 时,通过反射调用类的构造方法创建实例;在属性填充时,通过反射调用 setter 方法或直接访问字段来注入依赖-15-。
3. 核心执行流程
容器启动到 Bean 就绪,大致经历三个阶段:
加载配置元数据:解析 XML、注解或 Java Config,将扫描到的类封装为
BeanDefinition(Bean 的“说明书”)。注册 BeanDefinition:将
BeanDefinition存入注册表(本质是一个Map<String, BeanDefinition>)。实例化与依赖注入:根据
BeanDefinition,通过反射创建对象,再通过反射完成依赖注入-15。
4. 三级缓存(解决循环依赖)
当两个 Bean 相互依赖时(A 依赖 B,B 依赖 A),Spring 通过三级缓存机制解决:
一级缓存(singletonObjects) :存放完全初始化好的单例 Bean。
二级缓存(earlySingletonObjects) :存放半成品 Bean(仅实例化,未完成属性填充)。
三级缓存(singletonFactories) :存放
ObjectFactory,用于提前暴露 Bean 引用。
注意:构造器注入的循环依赖无法解决,这是构造器注入的“缺点”之一,也是为什么需要谨慎设计依赖关系-40。
七、高频面试题与参考答案
Q1:IoC 和 DI 的区别是什么?
踩分点:思想 vs 实现 + 二者关系 + 一句话总结
IoC(控制反转)是一种设计思想,它将对象的创建和依赖管理权从应用程序代码转移到外部容器。DI(依赖注入)是 IoC 的具体实现方式,指容器在创建对象时将依赖自动注入。两者是“思想与手段”的关系:IoC 是 What,DI 是 How。一句话:IoC 是设计原则,DI 是实现技术,Spring 通过 DI 实现了 IoC 容器。-3-
Q2:Spring 中有哪几种依赖注入方式?分别有什么优缺点?
踩分点:三种方式 + 推荐构造器注入的原因
三种方式:
构造器注入:依赖通过构造方法传入,推荐使用。优点是不可变性(
final)、测试友好、空安全、便于检测循环依赖。Setter 注入:依赖通过 setter 方法传入,适合可选依赖或需要动态修改的场景。
字段注入:直接
@Autowired字段,代码最简洁,但导致代码与 Spring 容器耦合、无法声明final、不利于测试,不推荐在生产代码中使用-3。
Q3:Spring 如何解决循环依赖?
踩分点:三级缓存机制 + 适用条件 + 不能解决的场景
Spring 通过三级缓存解决 setter 注入和字段注入下的单例 Bean 循环依赖:
一级缓存
singletonObjects:完全初始化好的 Bean。二级缓存
earlySingletonObjects:半成品 Bean。三级缓存
singletonFactories:提前暴露 Bean 的引用。
当 A 依赖 B、B 依赖 A 时,Spring 先将 A 的 ObjectFactory 存入三级缓存,在 B 创建完成后,A 能从缓存中获取到 B 的引用,从而完成装配。构造器注入的循环依赖无法解决-40-13。
Q4:BeanFactory 和 ApplicationContext 有什么区别?
踩分点:初始化时机 + 功能扩展 + 使用场景
| 维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 初始化时机 | 懒加载(首次 getBean() 时才创建) | 容器启动时立即初始化所有单例 Bean |
| 功能 | 仅基础 IoC 能力 | 扩展了国际化、事件发布、资源加载、AOP 支持等 |
| 使用场景 | 轻量级场景或资源受限环境 | 企业级开发(日常首选) |
ApplicationContext 是 BeanFactory 的子接口,日常开发中通常使用 AnnotationConfigApplicationContext 或 ClassPathXmlApplicationContext-15-8。
八、结尾总结
回顾全文,我们一起梳理了:
| 知识点 | 核心要点 |
|---|---|
| ✅ 痛点 | 传统代码高耦合、难测试、扩展性差 |
| ✅ IoC | 设计思想——控制权从代码反转到容器 |
| ✅ DI | 实现手段——依赖通过构造器/Setter/字段注入 |
| ✅ 关系 | IoC 是 What,DI 是 How,Spring 用 DI 实现 IoC |
| ✅ 代码 | @Autowired 工作流程:按类型 → 唯一/多个/冲突解决 |
| ✅ 原理 | 工厂模式 + 反射 + 三级缓存 |
| ✅ 面试 | IoC vs DI、三种注入方式、循环依赖、BeanFactory vs ApplicationContext |
易错点提醒:不要把 IoC 和 DI 当成同一个概念去背答案,面试官很在意你能不能讲清“思想”与“实现”的区别。另外,字段注入虽然方便,但建议在正式项目中优先使用构造器注入。
预告
下一篇文章我们将深入 Spring AOP(面向切面编程) ,从 JDK 动态代理和 CGLIB 的区别讲起,带你搞懂 @Transactional 失效的原因、切面执行顺序以及自定义注解切面的实战技巧。欢迎持续关注!
文中相关完整代码示例已整理成项目,关注【领航 AI 助手】回复“SpringIoC”即可获取。
📌 本文关键词:Spring IoC · 依赖注入 · 控制反转 · 面试题 · 底层原理 · 构造器注入 · 循环依赖 · 三级缓存
扫一扫微信交流