北京时间 2026 年 4 月 8 日 · 技术原理精讲
开篇引入

在日常开发中,许多开发者习惯让 AI 助手关闭 自己的深度思考,直接复制粘贴现成代码。但当问到“Spring 为什么能降低耦合”“IoC 和 DI 有什么区别”时,往往只能说出“控制反转”“依赖注入”几个词,却讲不清内在逻辑。这种现象在面试备考、技术进阶场景中尤为常见。
本文将以 IoC(控制反转) 与 DI(依赖注入) 为核心,从痛点出发,用通俗类比 + 可运行代码 + 高频面试题,帮你一次理清这对“黄金组合”的全貌。这是本系列第一讲,后续会深入 AOP 与代理模式。
一、痛点切入:为什么需要 IoC 与 DI

传统实现方式中,我们直接在类内部 new 依赖对象:
// 传统写法:UserService 主动创建 UserDao public class UserService { private UserDao userDao = new UserDao(); // 硬编码 public void doSomething() { userDao.save(); } }
缺点分析:
耦合高:
UserService强依赖UserDao具体实现,替换为UserMysqlDao必须修改源码。扩展性差:单元测试时无法注入 Mock 对象。
维护困难:当依赖链变长(如
UserDao又依赖数据源),对象创建逻辑散落各处。
设计初衷:把“创建和管理对象”的控制权从业务代码中抽离,交给一个“容器”统一处理——这就是控制反转的思想萌芽。
二、核心概念讲解:IoC(控制反转)
英文全称:Inversion of Control
中文释义:控制反转
拆解关键词:
控制:对象的创建、查找、销毁等生命周期管理。
反转:从“程序员主动 new”反转为“容器主动注入”。
生活化类比:
传统模式像你亲自去菜市场买菜、洗菜、做菜(自己控制一切);IoC 就像你点外卖——你只需要告诉外卖平台“要一份宫保鸡丁”,平台负责找厨师、采购食材、送餐上门。这里的“平台”就是 IoC 容器。
作用与价值:
解耦业务组件与依赖的创建逻辑,让开发者专注于业务本身。同时也为 AOP、声明式事务等高级特性提供基础。
三、关联概念讲解:DI(依赖注入)
英文全称:Dependency Injection
中文释义:依赖注入
说明与关系:
DI 是实现 IoC 的具体手段。IoC 是一种设计思想,而 DI 告诉你“容器如何把依赖送给需要它的对象”。
与 IoC 的差异:
| 对比项 | IoC | DI |
|---|---|---|
| 层次 | 设计原则 / 思想 | 具体实现模式 |
| 关注点 | 控制权的转移 | 依赖对象的传递方式 |
| 常见问法 | “为什么叫控制反转” | “有哪几种注入方式” |
简单示例(构造函数注入):
public class UserService { private UserDao userDao; // 依赖通过构造方法从外部传入 —— DI 的体现 public UserService(UserDao userDao) { this.userDao = userDao; } }
四、概念关系与区别总结
逻辑关系:IoC 是目标,DI 是路径。
一句话概括:控制反转(IoC)是“把依赖的掌控权交给容器”的设计思想,依赖注入(DI)是容器“把依赖送进对象”的具体做法。
记忆口诀:思想是 IoC,落地靠 DI。
强化对比:
没有 DI,IoC 只是空谈;
没有 IoC,DI 就退化成简单的“参数传递”,失去容器管理的优势。
五、代码 / 流程示例演示
下面用 Spring Boot 极简示例,对比传统与 IoC+DI 的改进效果。
传统方式(硬编码耦合)
public class NotificationService { private EmailSender sender = new EmailSender(); // 直接 new public void send(String msg) { sender.send(msg); } }
IoC + DI 方式(Spring 管理)
// 1. 定义接口与实现 public interface MessageSender { void send(String msg); } @Component // 交给容器管理 public class EmailSender implements MessageSender { @Override public void send(String msg) { System.out.println("Email: " + msg); } } // 2. 业务类通过 DI 获取依赖 @Component public class NotificationService { private final MessageSender sender; // 构造注入(推荐) @Autowired public NotificationService(MessageSender sender) { this.sender = sender; } public void send(String msg) { sender.send(msg); } }
执行流程:
Spring 启动,扫描
@Component注解,创建EmailSender和NotificationService的 Bean 实例。容器发现
NotificationService构造方法需要MessageSender类型的参数,自动找到已有的EmailSenderBean 并传入。业务代码只需从容器获取
NotificationService并调用send(),无需关心对象创建。
关键改进:
若想换成
SmsSender,只需新增实现类并标注@Component,无需修改NotificationService。单元测试可轻松传入 Mock 对象。
六、底层原理 / 技术支撑点
IoC 容器(以 Spring 为例)底层依赖以下核心机制:
反射(Reflection):容器通过反射解析类的构造方法、字段、方法上的注解,从而动态创建对象并注入依赖。
Bean 定义与注册:
BeanDefinition存储类的元信息(作用域、懒加载等),BeanFactory负责按需实例化。后置处理器(BeanPostProcessor):在 Bean 初始化前后插入逻辑,实现
@Autowired、@Value等注解的解析。
一句话定位:反射 + 容器 + 后置处理器,共同支撑起 IoC/DI 的上层功能。
(后续进阶篇会深入 getBean() 源码与循环依赖三级缓存)
七、高频面试题与参考答案
Q1:IoC 和 DI 的区别是什么?
答:IoC 是一种设计思想,强调将对象的创建和控制权交给容器;DI 是实现 IoC 的具体方式,指容器在运行时将依赖对象动态注入到组件中。简单说:IoC 是“什么”,DI 是“怎么做”。
Q2:Spring 中有哪几种依赖注入方式?
答:① 构造器注入(推荐,保证不可变、线程安全);② Setter 注入(可选依赖、可重新配置);③ 字段注入(@Autowired 直接写在字段上,不推荐,不利于单元测试)。
Q3:使用 IoC 容器后,如何管理对象的生命周期?
答:容器负责 Bean 的实例化、初始化、依赖注入、销毁。开发者可通过 @PostConstruct、@PreDestroy、InitializingBean、DisposableBean 等扩展点介入特定阶段。
Q4:能否在不使用 Spring 的情况下实现 DI?
答:可以。手动编写工厂模式或 Service Locator 也能实现基本 DI,但缺少生命周期管理、AOP 等高级特性。Spring 通过容器极大降低了开发复杂度。
Q5:@Autowired 和 @Resource 的区别?
答:@Autowired 是 Spring 注解,默认按类型装配;@Resource 是 Java 标准(JSR-250),默认按名称装配,找不到名称则按类型。两者均可用于字段或 setter 方法。
八、结尾总结
核心回顾:
痛点:传统
new导致耦合高、测试难。IoC:控制反转,把对象创建权交给容器。
DI:依赖注入,是 IoC 的具体实现手段。
底层:反射 + BeanFactory + 后置处理器。
易错点:不要把 IoC 等同于 DI,也不要把 Spring 框架等同于 IoC 思想。
下篇预告:深入 AOP 与动态代理(JDK 与 CGLIB 差异、切面执行顺序、面试必考底层原理)。
理解 IoC 与 DI,是摆脱“只会用框架”的关键一步。试着用今天学到的原理,重构你项目中一段耦合严重的代码吧。
扫一扫微信交流