北京时间 2026 年 4 月 8 日发布
一、开篇引入

如果你正在学习 Spring,那么你一定听说过 IoC(Inversion of Control,控制反转) 和 DI(Dependency Injection,依赖注入) 这两个词。它们不仅是 Spring 框架的核心,更是理解 Spring 底层原理的第一步。
很多学习者在接触这两个概念时,常常遇到这样的困境:

只会用注解:每天写着
@Autowired、@Service,但一旦问到“为什么要这么写”,就说不清了;概念混淆:搞不清 IoC 和 DI 到底是什么关系,面试时回答得模棱两可;
原理模糊:知道 Spring 容器管理对象,但不清楚它是怎么做到的。
如果你也有以上困惑,这篇文章就是为你准备的。
本文将从“痛点切入”出发,逐步拆解 IoC 与 DI 的本质,带你从“会用 Spring”升级为“理解 Spring”。 作为系列文章的第一篇,我们将重点聚焦在 IoC/DI 的核心概念、关系与底层原理上,后续会深入 Bean 生命周期、AOP 等进阶内容。
二、痛点切入:为什么需要 IoC?
2.1 传统开发模式
在传统的 Java 开发中,对象之间的依赖关系通常由开发者手动管理。来看一段典型的传统代码:
// 数据访问层实现类 public class UserDaoImpl implements UserDao { @Override public void queryUser() { System.out.println("查询用户信息"); } } // 服务层:手动创建依赖对象 public class UserServiceImpl implements UserService { // 主动 new 依赖对象,控制权在开发者手中 private UserDao userDao = new UserDaoImpl(); @Override public void queryUser() { userDao.queryUser(); } } // 测试类:手动创建所有对象 public class Test { public static void main(String[] args) { UserService userService = new UserServiceImpl(); userService.queryUser(); } }
2.2 痛点分析
这种写法的弊端非常明显:
| 痛点 | 说明 |
|---|---|
| 高耦合 | UserServiceImpl 与 UserDaoImpl 强绑定。如果需要将数据源从 MySQL 切换为 Oracle,必须修改 UserServiceImpl 的代码-2 |
| 扩展性差 | 新增 DAO 实现类时,需要修改所有依赖该 DAO 的服务类,违背“开闭原则”-36 |
| 维护困难 | 当对象数量增多时,手动管理所有依赖关系会让代码变得臃肿不堪 |
| 测试困难 | 难以对 UserService 进行单元测试——因为它内部固定创建了 UserDaoImpl,无法模拟 DAO 层的返回结果-36 |
这些痛点的根源在于:对象“主动”创建并管理了自己的依赖。有没有办法把这些“创建依赖”的脏活累活交给别人来做?
三、IoC:让对象“躺平”的设计思想
3.1 官方定义
IoC(Inversion of Control,控制反转) 是一种设计思想,其核心是:对象的创建与依赖关系的管理,不再由程序代码主动控制,而是交给外部容器来完成-4。
3.2 拆解关键词
“控制”:指对象创建、依赖装配、生命周期管理的主动权;
“反转”:将这个主动权从开发者代码中“转移”给外部容器;
“外部容器”:在 Spring 中,这个容器就是 IoC 容器(如
ApplicationContext)。
简单来说,IoC 就是让对象“躺平”——不再自己操心依赖从哪来,而是等着容器把依赖“送上门”。
3.3 生活类比
想象一下组织家庭聚餐:
传统模式:你要亲自列食材清单 → 去超市采购 → 洗菜备菜 → 炒菜做饭。每一步都不能省,少一样菜就做不了。
IoC 模式:你只需要告诉上门厨师“周末中午 10 人聚餐,要 3 个热菜、2 个凉菜”。厨师会自己列清单、采购、备菜、做菜,最后直接端上桌——你只管招呼客人,专注享受聚餐-16。
IoC 的核心价值不是“少写几行代码”,而是“彻底解耦”——就像你和菜场解耦,不关心菜场在哪、食材多少钱。
四、DI:IoC 的具体落地手段
4.1 官方定义
DI(Dependency Injection,依赖注入) 是指:一个类所依赖的对象不由其自身创建,而是由外部容器创建并注入到该类中,从而实现类与类之间的解耦-36。
4.2 DI 的三种实现方式
Spring 提供了三种依赖注入方式-11:
| 注入方式 | 实现方式 | 推荐度 | 特点 |
|---|---|---|---|
| 构造器注入 | 通过构造函数参数传入依赖 | ⭐⭐⭐ 最推荐 | 依赖不可变,便于测试,避免循环依赖 |
| Setter 注入 | 通过 Setter 方法设置依赖 | ⭐⭐ 可选 | 灵活,支持可选依赖 |
| 字段注入 | 直接在字段上用 @Autowired | ⭐ 谨慎使用 | 代码最简洁,但破坏封装,不利于测试 |
推荐使用构造器注入:它可以确保依赖在对象创建时就绪,且支持不可变性,是 Spring 官方推荐的方式。
4.3 DI 的代码示例
下面是用 Spring 重构后的代码:
// 数据访问层:由 Spring 容器管理 @Repository public class UserDaoImpl implements UserDao { @Override public void queryUser() { System.out.println("查询用户信息"); } } // 服务层:依赖由容器注入 @Service public class UserServiceImpl implements UserService { // 仅声明依赖,不主动创建 private UserDao userDao; // 构造器注入(推荐方式) @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } @Override public void queryUser() { userDao.queryUser(); } } // 测试类:从容器中获取对象,无需手动管理依赖 public class Test { public static void main(String[] args) { // 容器初始化,自动创建 Bean、装配依赖 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 直接获取对象,依赖已自动注入 UserService userService = context.getBean(UserService.class); userService.queryUser(); } }
4.4 对比小结
| 维度 | 传统模式 | IoC/DI 模式 |
|---|---|---|
| 依赖创建者 | 开发者(手动 new) | Spring 容器 |
| 依赖管理方式 | 硬编码 | 配置/注解声明 |
| 耦合度 | 高耦合 | 低耦合 |
| 可测试性 | 差(无法 Mock) | 好(可轻松替换实现) |
| 代码可读性 | 依赖关系隐式 | 依赖关系显式 |
五、IoC vs. DI:一句话说清
很多开发者对这两个概念容易混淆,下面用一张图帮你彻底厘清:
| 维度 | IoC | DI |
|---|---|---|
| 本质 | 设计思想 | 实现方式 |
| 角色 | 设计层面(What) | 技术层面(How) |
| 关系 | 思想指导 | 手段落地 |
| 核心 | “控制权交给容器” | “依赖由容器注入” |
一句话概括:IoC 是一种设计思想——让容器接管对象的创建和管理;DI 是实现这一思想的具体手段——容器主动将依赖“注入”给对象。
打个比方:IoC 就像是“你告诉厨师你要吃什么菜”,而 DI 就是“厨师把做好的菜端到你面前”这个动作本身-16。
六、代码对比:从传统到 Spring 的演进
为了让你更直观地感受 IoC/DI 带来的变化,下面分别用 XML 配置 和 注解配置 两种方式展示 Spring 的配置演进。
6.1 XML 配置方式(早期 Spring)
<!-- applicationContext.xml --> <bean id="userDao" class="com.example.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.example.service.impl.UserServiceImpl"> <constructor-arg ref="userDao"/> </bean>
6.2 Java 配置方式(主流推荐)
@Configuration public class AppConfig { @Bean public UserDao userDao() { return new UserDaoImpl(); } @Bean public UserService userService(UserDao userDao) { return new UserServiceImpl(userDao); } }
6.3 注解配置方式(最简洁,日常开发首选)
@Configuration @ComponentScan("com.example") public class AppConfig { // 无需显式定义 @Bean,通过 @ComponentScan 自动扫描 }
Spring 配置方式从 XML → Java Config → 注解,越来越简洁,但背后的 IoC/DI 核心逻辑始终如一。
💡 2026 年的 Spring 趋势:Spring Framework 6.x 已全面进入 Java 17+ 时代,全面支持 Jakarta EE 9/10(包名从 javax. 迁移至 jakarta.),并引入 AOT 编译优化,支持 GraalVM Native Image,让 Spring 应用可以编译成原生二进制文件,实现毫秒级启动-30-45。
七、底层原理:反射 + 设计模式
7.1 技术支撑点
IoC/DI 的底层核心实现依赖于两个关键技术:
Java 反射机制:容器在运行时通过反射获取类的构造器、方法和字段信息,动态创建对象并注入依赖;
设计模式:工厂模式(IoC 容器本质上是一个超级工厂)、模板方法模式(如
refresh()方法定义容器启动骨架)等。
一句话总结:Spring IoC 本质上是 Spring 容器接管了对象的创建、依赖注入、销毁等全流程,底层靠 “反射 + 设计模式” 实现-1。
7.2 IoC 容器的核心流程
以最常用的「注解配置」为例,IoC 容器从启动到创建 Bean 的核心步骤如下-1:
| 步骤 | 核心动作 | 说明 |
|---|---|---|
| 1. 容器初始化 | 加载配置元数据 | 解析配置类(如 @Configuration)或扫描指定包,识别需要管理的类 |
| 2. 注册 BeanDefinition | 封装 Bean 信息 | 将扫描到的类封装为 BeanDefinition(Bean 的“说明书”),注册到容器中 |
| 3. Bean 实例化 | 反射创建对象 | 容器根据 BeanDefinition,通过反射调用构造器创建 Bean 实例 |
| 4. 依赖注入 | 属性填充 | 容器解析依赖关系(如 @Autowired),通过反射将依赖对象注入 |
| 5. 初始化 | 执行初始化逻辑 | 调用 @PostConstruct、InitializingBean 等初始化方法 |
| 6. 销毁 | 执行销毁逻辑 | 容器关闭时,调用 @PreDestroy、destroy-method 等销毁方法 |
💡 核心数据结构:BeanDefinitionRegistry 本质上是一个 Map<String, BeanDefinition>,key 是 Bean 名称,value 是 Bean 的定义信息-1。
八、高频面试题
Q1:什么是 IoC?什么是 DI?两者的关系是什么?
参考答案:
IoC(控制反转) 是一种设计思想,它将对象的创建、依赖管理、生命周期控制的权利从开发者代码转移到外部容器。
DI(依赖注入) 是实现 IoC 的具体手段,指容器在创建对象时,自动将该对象需要的依赖“注入”进去。
关系:IoC 是“思想”,DI 是“实现”——Spring 通过 DI 来实现 IoC-21。
踩分点:答出“思想 vs 实现”的关系、提到“控制权转移”即可拿高分。
Q2:Spring 中 BeanFactory 和 ApplicationContext 的区别?
参考答案:
| 维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 加载时机 | 懒加载(getBean 时才创建) | 预加载(启动时初始化所有单例 Bean) |
| 功能范围 | 仅提供基本的 IoC 功能 | 扩展了 AOP、事件发布、国际化、资源加载等 |
| 使用场景 | 资源受限的环境 | 绝大多数企业项目-4 |
踩分点:强调懒加载 vs 预加载、功能范围差异、ApplicationContext 是 BeanFactory 的超集。
Q3:Spring 中有哪几种依赖注入方式?推荐使用哪种?
参考答案:Spring 支持三种依赖注入方式:构造器注入、Setter 注入 和 字段注入。官方推荐使用构造器注入,因为:
保证依赖不可变(
final修饰);便于单元测试(依赖一目了然);
避免循环依赖问题-。
踩分点:答出三种方式并说明推荐构造器注入的理由。
Q4:Spring IoC 容器底层依赖什么技术?
参考答案:Spring IoC 底层主要依赖 Java 反射机制 和 工厂模式。反射用于在运行时动态获取类信息、创建对象、调用方法、赋值属性;工厂模式体现在 IoC 容器本身就是一个管理对象创建和分发的超级工厂-1。
踩分点:提到“反射”和“工厂模式”就是满分。
Q5:如何将类交给 Spring IoC 容器管理?
参考答案:主要有三种方式:
注解方式:在类上添加
@Component、@Service、@Repository、@Controller等注解,配合@ComponentScan自动扫描-12;Java Config 方式:在
@Configuration类中使用@Bean方法定义 Bean;XML 配置方式:在
applicationContext.xml中配置<bean>标签(旧项目常见)。
踩分点:答出三种方式并说明当前主流是注解 + Java Config。
九、结尾总结
9.1 核心知识点回顾
IoC(控制反转) :一种设计思想,将对象创建和依赖管理的控制权从开发者转移到容器;
DI(依赖注入) :IoC 的具体实现方式,由容器主动将依赖“注入”给对象;
核心关系:IoC 是思想(What),DI 是手段(How),Spring 通过 DI 实现 IoC;
底层原理:依赖 Java 反射机制 + 工厂模式 实现;
最佳实践:推荐使用构造器注入,确保依赖的不可变性和可测试性。
9.2 易错点提醒
❌ 不要把 IoC 和 DI 说成“两个并列的东西”——它们是“思想”与“实现”的关系;
❌ 不要忽略反射机制的重要性——这是 IoC 容器能够“动态管理对象”的技术根基;
❌ 字段注入虽然方便,但会影响可测试性,生产项目中应谨慎使用。
9.3 预告
本篇是 Spring IoC/DI 原理系列的第一篇。下一篇我们将深入 Bean 的生命周期,详细拆解 Spring 容器是如何管理 Bean 从“出生”到“销毁”的全过程的,敬请期待!
📌 本文首发于 AI 复习助手,欢迎收藏、转发、点赞!
参考资料:Spring Framework 官方文档、CSDN、掘金、imooc 等社区优质文章。