北京时间 2026年4月8日
写在前面:本文为 Spring IOC/DI 系列文章第一篇,聚焦核心概念、底层反射原理与面试高频考点。系列后续将深入 Bean 生命周期、循环依赖解决方案、AOP 底层动态代理等进阶内容。

一、开篇引入
对任何一名 Java 后端开发者来说,Spring 几乎是绕不开的框架基石。而在 Spring 庞大生态的背后,支撑一切的核心只有一个——IoC 容器。

IoC(Inversion of Control,控制反转)与 DI(Dependency Injection,依赖注入)是 Spring 框架最核心的两个概念,也是面试中出现频率最高的考点之一。许多学习者的痛点在于:天天用 @Autowired、天天写 @Service,却说不清 IoC 和 DI 到底是什么关系,更答不出“Spring 底层是如何把对象创建出来的”。
本文将从“为什么需要这个技术”出发,讲清 IoC 与 DI 的定义、关系与底层实现原理,并用极简代码示例帮你建立完整知识链路。
二、痛点切入:为什么需要 IoC?
先看一段传统 Java 代码:
// 传统方式:业务层直接 new 依赖对象 public class UserService { private UserDao userDao = new MySQLUserDao(); // 写死了具体实现 public void register(String username) { userDao.save(username); } }
这个写法有什么问题?
| 痛点 | 说明 |
|---|---|
| 高耦合 | UserService 直接依赖 MySQLUserDao 具体类,而非接口 |
| 扩展性差 | 要换成 OracleUserDao 或 RemoteUserDao,必须改源码 |
| 维护困难 | 多个类各自 new 对象,依赖关系分散,难以统一管理 |
| 可测试性差 | 单元测试时难以 mock 依赖对象 |
当业务增长、实现类频繁变更时,这种“硬编码 new”的模式会让代码迅速失控。有没有一种方式能让业务类只依赖抽象接口,而把“谁来帮我创建对象”这件事交给外部处理? IoC 由此诞生。
三、核心概念讲解:IoC(控制反转)
定义: IoC(Inversion of Control,控制反转)是一种设计思想,其核心是将对象的创建权与依赖关系的管理权,从程序代码内部“反转”给外部容器(如 Spring IoC 容器)来统一管理。-4
关键词拆解:
控制:指的是“谁来负责对象的创建和依赖的装配”。
反转:传统模式下,程序主动
new对象是“正转”;将控制权交给容器,是“反转”。
生活类比: 以前自己做饭(传统模式),你要自己去菜市场买菜、洗菜、切菜、下锅——所有事情亲力亲为。现在叫外卖(IoC),你只需告诉平台“我要一份番茄炒蛋盖饭”,平台(容器)会帮你完成采购、加工、配送的全过程,你只管吃。-47
核心作用: 实现对象之间的解耦。业务类不再关心依赖对象从哪来、怎么创建,只声明“我需要什么”,容器自动装配。
四、关联概念讲解:DI(依赖注入)
定义: DI(Dependency Injection,依赖注入)是 IoC 思想的一种具体实现方式。它指的是容器在创建对象的过程中,自动将当前对象所依赖的其他对象,通过构造函数、Setter 方法或字段等方式“注入”进来。-
IoC 与 DI 的关系(重点):
| 维度 | IoC(控制反转) | DI(依赖注入) |
|---|---|---|
| 定位 | 设计思想 | 技术实现 |
| 做什么 | 描述“控制权转移”这一理念 | 描述“如何把依赖送进去”的具体动作 |
| 关系 | 目标 | 手段 |
一句话总结:IoC 是一种思想,DI 是实现这种思想的手段。 -4
三种常见的 DI 方式:
| 注入方式 | 实现方式 | 优点 | 适用场景 |
|---|---|---|---|
| 构造器注入 | 通过带参构造方法传入依赖 | 强制依赖检查,支持不可变对象 | 推荐方式,核心必选依赖 |
| Setter 注入 | 通过 setter 方法设置依赖 | 可选依赖,可重新注入 | 可选依赖或配置变更场景 |
| 字段注入 | 直接用 @Autowired 注解 | 简洁 | 简单场景,但可测试性差 |
五、概念关系总结
┌─────────────────────────────────────────────────────────┐ │ IoC(设计思想) │ │ “把对象创建的权力交给外部容器来管” │ └─────────────────────────┬───────────────────────────────┘ │ ▼ 通过以下方式实现 ┌─────────────────────────────────────────────────────────┐ │ DI(依赖注入) │ 依赖查找(DL) │ 上下文查找 │ ... │ │ “容器主动把依赖送进来” “主动去容器里找” │ └─────────────────────────────────────────────────────────┘
Spring 中 IoC 与 DI 的关系:IoC 是目标(做什么),DI 是手段(怎么做)。 面试中如果被问到区别,按这个层次回答即可得分。
六、代码示例:对比传统方式与 Spring IoC/DI
传统方式(耦合高):
// DAO层 public interface UserDao { void save(String username); } public class MySQLUserDao implements UserDao { @Override public void save(String username) { System.out.println("保存用户[" + username + "]到 MySQL"); } } // Service层 —— 主动 new 依赖,写死了具体实现 public class UserService { private UserDao userDao = new MySQLUserDao(); // ❌ 强耦合 public void register(String username) { userDao.save(username); } }
Spring IoC/DI 方式(低耦合):
// DAO层 —— 交给容器管理 @Repository // 告诉Spring:这个类由容器管理 public class MySQLUserDao implements UserDao { @Override public void save(String username) { System.out.println("保存用户[" + username + "]到 MySQL"); } } // Service层 —— 依赖由容器注入,不再主动new @Service public class UserService { @Autowired // 告诉Spring:我需要一个UserDao的实例,请帮我注入 private UserDao userDao; // 声明依赖,不创建 public void register(String username) { userDao.save(username); } } // 测试代码 —— 从容器获取对象,依赖自动装配 public class Application { public static void main(String[] args) { // 启动 Spring 容器 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 从容器获取 UserService,依赖已自动注入 UserService userService = context.getBean(UserService.class); userService.register("张三"); } }
核心变化:
UserService中不再有new关键字使用
@Autowired声明依赖需求容器启动时自动完成对象创建和依赖注入
七、底层原理 / 技术支撑:反射
IoC 容器之所以能在运行时动态创建对象、注入依赖,底层依赖的核心技术是 Java 反射(Reflection) 。--
什么是反射?
反射允许程序在运行时动态获取任意类的完整信息(类名、方法、字段、构造器等),并动态创建实例、调用方法、访问属性——而这些在编译期是完全未知的。-
反射在 Spring IoC 容器中的作用:
| 阶段 | 反射的应用 |
|---|---|
| 读取配置 | 容器扫描 @Component、@Service 等注解,获取需要管理的类 |
| 实例化 Bean | 通过 Constructor.newInstance() 动态调用构造器创建对象 |
| 依赖注入 | 通过 Field.set() 给 @Autowired 标记的私有字段赋值 |
| 调用方法 | 调用初始化方法(如 @PostConstruct) |
注:private 字段默认不可访问,Spring 在注入时会调用 field.setAccessible(true) 临时开放访问权限。在 JDK 17+ 中,final 字段的反射修改会受到限制。-42
容器启动核心流程(简化版):
1. 容器初始化 → 加载配置元数据 2. 解析 @Component 等注解 → 封装为 BeanDefinition(Bean 的“说明书”) 3. 注册 BeanDefinition 到容器(存在一个 Map 中) 4. 根据 BeanDefinition,通过反射实例化 Bean 5. 根据 @Autowired,通过反射完成依赖注入 6. 执行初始化回调 7. Bean 准备就绪,供业务使用
上述流程中,步骤 4 和步骤 5 直接依赖反射技术,没有反射,就没有 Spring 的 IoC 容器。-1
八、高频面试题与参考答案
Q1:什么是 IoC?什么是 DI?两者的关系是什么?
参考答案:
IoC(控制反转) 是一种设计思想,将对象的创建权、依赖管理权从代码内部“反转”给外部容器。
DI(依赖注入) 是实现 IoC 的具体技术手段,容器在创建对象时自动将依赖注入进去。
关系:IoC 是设计目标(做什么),DI 是实现手段(怎么做)。IoC 通过 DI 来完成依赖管理。-
Q2:Spring IoC 容器底层是如何实现依赖注入的?
参考答案:
Spring 底层依赖 Java 反射机制。具体步骤:① 容器读取配置,将要管理的类封装为 BeanDefinition;② 遍历 BeanDefinition,通过 Constructor.newInstance() 反射创建对象实例;③ 扫描 @Autowired 等注解,通过 Field.set(obj, value) 反射将依赖对象注入到目标对象中。--1
Q3:构造器注入、Setter 注入和字段注入有什么区别?Spring 推荐哪种?
参考答案:
构造器注入:通过构造方法传入依赖,支持不可变对象,强制依赖检查,推荐用于必选依赖。
Setter 注入:通过 setter 方法设置,适合可选依赖或可重新配置的场景。
字段注入(
@Autowired直接写在字段上):最简洁,但可测试性较差。Spring 官方推荐:构造器注入。它确保依赖在对象创建时就已经完整,便于编写不可变对象,且利于单元测试。--49
Q4:BeanFactory 和 ApplicationContext 有什么区别?
参考答案:
BeanFactory:Spring 最基础的 IoC 容器接口,采用懒加载(首次调用
getBean()时才创建对象),功能精简。ApplicationContext:继承自 BeanFactory,采用预加载(容器启动时创建所有单例 Bean),扩展了国际化、事件发布、AOP 等企业级功能。
日常开发使用 ApplicationContext,BeanFactory 一般仅用于资源受限的场景。-1-2
九、结尾总结
本文核心回顾:
| 知识点 | 核心要点 |
|---|---|
| IoC | 设计思想,控制权从代码转移给容器 |
| DI | 实现手段,容器将依赖“注入”到对象中 |
| 关系 | IoC 是目标,DI 是手段 |
| 底层原理 | 依赖 Java 反射技术(Constructor.newInstance()、Field.set()) |
| 面试核心 | 能说清概念、答出关系、讲出反射原理 |
易错点提示:
❌ 把 IoC 和 DI 混为一谈,说不清关系
❌ 只背概念,不懂底层用反射实现
❌ 不知道构造器注入是官方推荐方式
下一讲预告: 《Spring IOC/DI 原理(二):Bean 生命周期与循环依赖解决方案》
思考题: 既然反射会带来一定的性能开销(相比直接 new 对象),为什么 Spring 仍然能支撑起海量请求的企业级应用?Spring 在设计上做了哪些优化来弥补反射的性能问题?欢迎在评论区留下你的思考。