本文导读:本文面向希望从“会用 Spring”升级到“理解 Spring”的读者,通过传统开发的痛点切入,系统讲解控制反转(IoC)与依赖注入(DI)的核心概念、关系辨析、代码示例及底层原理,并提供高频面试题答案。全文约 3500 字,阅读时间约 10 分钟。
📅 基本信息

| 项目 | 内容 |
|---|---|
| 文章标题 | Spring 的 IoC/DI 原理(一):从“主动 new”到“容器接管”的底层变革 |
| 发布时间 | 北京时间 2026 年 4 月 8 日 |
| 目标读者 | 技术入门 / 进阶学习者、在校学生、面试备考者、Java/Spring 开发工程师 |
| 文章定位 | 技术科普 + 原理讲解 + 代码示例 + 面试要点 |
| Spring 版本背景 | Spring Framework 6.x(2026 年主流版本),Spring Framework 7.0 已于 2025 年 11 月正式发布,Spring Boot 4.0 起强制要求 JDK 17+--51 |
一、开篇引入
Spring 框架自 2003 年诞生以来,已成为 Java 后端开发的“基石框架”,全球数百万个项目依赖它运行-31。而在 Spring 庞大的技术生态中,IoC(控制反转)与 DI(依赖注入) 被公认为 Spring 的“灵魂”——Spring 框架中超过 80% 的核心模块直接或间接依赖 IoC 容器提供的服务-。

很多 Spring 初学者有这样的困惑:
✅ 会用
@Autowired和@Service,但被问到“IoC 是什么”就说不清楚✅ 项目中天天用 Spring,但不知道底层到底发生了什么
✅ IoC 和 DI 概念混淆,面试时说不清两者的关系
✅ 遇到依赖注入失效的 bug(如空指针异常),无从下手排查
本文作为《Spring IoC/DI 原理》系列的第一篇,将从传统开发的痛点切入,系统讲解 IoC 与 DI 的核心概念、底层原理,并提供可直接运行的代码示例和高频面试题。后续系列将深入 Bean 生命周期、AOP 原理、循环依赖的解决机制等内容。
阅读完本文,你将能够: 说清 IoC 与 DI 的定义和关系、理解控制权转移的本质、掌握依赖注入的三种方式、看懂 Spring 容器的底层执行流程、从容应对相关面试题。
二、痛点切入:为什么需要 IoC 和 DI?
2.1 传统开发的“痛点代码”
在没有 Spring 框架的传统 Java 开发中,对象之间的依赖关系通常由开发者手动管理。看下面这段代码:
// 依赖对象:UserDaoImpl public class UserDaoImpl implements UserDao { @Override public void queryUser() { System.out.println("查询用户信息"); } } // 目标对象:UserServiceImpl,手动创建依赖 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 的源代码-1 |
| 扩展性差 | 每新增一个依赖对象,都需要在多处代码中手动 new 和管理 |
| 维护困难 | 对象数量增多时,手动管理所有依赖会让代码臃肿不堪,牵一发而动全身-1 |
| 可测试性差 | 无法在单元测试中轻松替换 Mock 对象,测试需要依赖完整的真实依赖链 |
2.3 IoC 的设计初衷
为了解决上述问题,IoC 的设计思想应运而生。其核心是将对象的创建权、依赖的装配权,从业务逻辑代码中转移到外部容器,由容器统一管理-1。这就像是“好莱坞原则”(Don‘t call us, we’ll call you)——你不要主动去找需要的对象,等着容器给你送过来-。
三、核心概念一:IoC(控制反转)
3.1 标准定义
IoC(Inversion of Control,控制反转)是一种软件设计原则,它将传统的程序设计中的控制权从应用程序代码转移到框架或容器,从而实现松耦合和更好的可维护性-。
3.2 关键词拆解
“控制” :指的是对象的创建权、依赖的管理权和生命周期的控制权。
“反转” :指的是控制权的转移——从开发者代码手中转移到 Spring 容器-1。
3.3 一句话理解
IoC 的本质判断标准是:“谁决定对象怎么创建?”
如果 A 类里直接
new B(),那么 A 控制着 B 的实例化;如果 A 的构造函数接收一个 B 实例(不管是谁传进来的),控制权就移交出去了-2。
3.4 生活化类比
以前自己办聚餐(传统开发模式),要亲自做三件事:列清单(确定依赖)、去超市采购(new 对象)、备菜做菜(关联依赖)。而现在请一个“上门厨师”(Spring 容器)帮忙(IoC 思想),你只需要告诉厨师“我要 3 个热菜”(声明需求),厨师会自己完成食材采购、备菜、做菜的全流程——你只管招呼客人(专注业务逻辑),这就是控制反转-25。
四、核心概念二:DI(依赖注入)
4.1 标准定义
DI(Dependency Injection,依赖注入)是实现 IoC 的核心手段。它指容器在创建对象时,会自动把该对象需要的依赖“主动送过去”,而不用开发者手动关联依赖关系-12。
4.2 DI 的三种实现方式
| 注入方式 | 实现方式 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| 构造器注入 | 通过类的构造函数传入依赖对象 | 依赖非空保证、不可变性、便于单元测试 | 无法解决循环依赖 | ⭐⭐⭐⭐⭐(大厂标配)- |
| Setter 注入 | 通过 Setter 方法注入依赖 | 灵活、支持可选依赖 | 依赖关系不明确,对象可能处于“半成品”状态- | ⭐⭐ |
| 字段注入(@Autowired) | 直接在字段上使用注解 | 代码简洁 | 无法声明为 final,可测试性差,Spring 官方不推荐- | ⭐ |
Spring 5.0+ 官方推荐:优先使用构造器注入(Constructor Injection)。构造器注入允许将依赖字段声明为 final,保证依赖不可变、非空,避免运行时空指针异常-。
4.3 注解配置示例(构造器注入)
// 1. 被依赖的 Service(@Service 声明为 Bean) @Service // Bean 名称默认:userServiceImpl public class UserServiceImpl implements UserService { // 业务逻辑 } // 2. 控制器类(构造器注入 - 官方推荐写法) @Controller public class UserController { // 必选依赖建议用 final 修饰 private final UserService userService; // ⭐ 语法糖:单个构造方法 → 无需加 @Autowired public UserController(UserService userService) { this.userService = userService; } }
如果类中有多个构造方法,需要显式使用 @Autowired 指定用于注入的构造器-11。
五、IoC 与 DI 的关系辨析
5.1 核心结论
| 维度 | IoC | DI |
|---|---|---|
| 定位 | 设计思想 / 设计原则 | 技术手段 / 落地方式 |
| 回答的问题 | “谁来管理对象的创建?” | “依赖如何传递进去?” |
| 比喻 | 思想:“让别人帮你统筹安排” | 动作:“别人具体帮你送东西”-25 |
5.2 一句话总结
IoC 是“想通了要把控制权交出去”的设计思想,DI 是“具体怎么交出去”的技术实现。
5.3 对比强化记忆
┌─────────────────────────────────────────────────────────┐ │ IoC(控制反转):对象的管理权归容器,开发者只管用 │ │ ↓ 通过 │ │ DI(依赖注入):把依赖对象“注入”到需要它的对象中 │ │ (构造器 / Setter / 字段) │ └─────────────────────────────────────────────────────────┘
在 Spring 框架中,DI 是实现 IoC 的唯一方式-12。两者不可分割:没有 DI,IoC 无法落地;没有 IoC 思想,DI 就只是一套注入机制。
六、代码示例:从“手动管理”到“容器接管”
6.1 IoC 模式下的代码
// 依赖对象:UserDao(无需手动 new) @Repository public class UserDaoImpl implements UserDao { @Override public void queryUser() { System.out.println("查询用户信息"); } } // 目标对象:UserService(依赖由容器注入) @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(); // 输出:查询用户信息 } }
6.2 核心变化总结
| 对比维度 | 传统模式 | IoC/DI 模式 |
|---|---|---|
| 依赖创建 | 手动 new 对象 | 容器自动创建 |
| 依赖注入 | 手动 setter / 构造传参 | 容器自动注入(@Autowired) |
| 控制权 | 掌握在开发者手中 | 转移到 Spring 容器 |
| 耦合度 | 高耦合,修改需改代码 | 低耦合,只需改配置 |
| 可测试性 | 难以 Mock | 易于 Mock 替换 |
七、底层原理 / 技术支撑
7.1 Spring IoC 容器的两大核心接口
| 接口 | 定位 | 特点 | 使用场景 |
|---|---|---|---|
| BeanFactory | 基础容器接口 | 懒加载,轻量级 | 资源受限环境 |
| ApplicationContext | 增强版容器 | 启动时创建所有单例 Bean,支持国际化、事件发布等 | 日常开发(SpringBoot 默认容器)-40-41 |
7.2 IoC 容器的核心执行流程
以注解配置方式为例,IoC 容器的底层执行分为三个核心步骤-41:
步骤 1:容器初始化(加载配置元数据) ↓ 扫描 @Component/@Service/@Repository 等注解的类 将扫描到的类封装为 BeanDefinition(Bean 的“说明书”) ↓ 步骤 2:注册 BeanDefinition 到容器 ↓ 存入 BeanDefinitionRegistry(本质是 Map<String, BeanDefinition>) ↓ 步骤 3:Bean 的实例化与依赖注入(核心) ↓ 通过反射调用构造器创建对象 → 属性填充(依赖注入)→ 处理 Aware 接口 → 执行初始化方法 → Bean 就绪放入单例池
7.3 底层技术支撑:反射
Spring 容器创建对象和注入依赖的底层核心是 Java 反射机制(Reflection) 。
容器在运行时通过
Class.forName()获取类的字节码信息通过
Constructor.newInstance()动态创建对象实例-2通过
Field.set()或Method.invoke()注入依赖-41
反射让 Spring 能够在编译时完全不知道具体类的情况下,在运行时动态地创建对象、调用方法、注入依赖。这为 IoC 的实现提供了最底层的基础能力。
7.4 底层设计模式支撑
除了反射,Spring IoC 容器底层还大量应用了经典设计模式-:
| 设计模式 | 在 IoC 中的体现 |
|---|---|
| 工厂模式 | BeanFactory 和 ApplicationContext 作为对象工厂,统一创建和管理 Bean |
| 单例模式 | 默认情况下,每个 Bean 在容器中只有一个实例(由 ConcurrentHashMap 实现单例注册表) |
| 模板方法模式 | Bean 生命周期中,容器定义了固定的执行流程,开发者只能在扩展点参与 |
⚠️ 注意:Spring 中的“单例”与 GoF 单例模式略有不同——它限定的是 “每个 Spring IoC 容器内唯一” ,而不是整个 JVM 进程唯一-。
八、高频面试题与参考答案
Q1:请解释 IoC 和 DI 分别是什么?它们之间的关系是什么?
参考答案(踩分点) :
IoC(Inversion of Control,控制反转) 是一种设计思想,它将传统上由程序代码直接操控的对象调用权交给容器(如 Spring 容器),通过容器来实现对象组件的装配和管理-21。
DI(Dependency Injection,依赖注入) 是实现 IoC 的核心技术手段,指容器在创建对象时,会自动把该对象需要的依赖“注入”进去。
两者的关系:IoC 是“设计思想”,DI 是“具体实现”。Spring 通过 DI 来实现 IoC-。简单说,IoC 是“想法”,DI 是“行动”。
Q2:Spring 支持哪几种依赖注入方式?官方推荐哪种?
参考答案:
| 方式 | 说明 | 推荐度 |
|---|---|---|
| 构造器注入 | 通过构造函数传入依赖,可声明为 final,保证非空 | ⭐⭐⭐⭐⭐ |
| Setter 注入 | 通过 Setter 方法注入,适合可选依赖 | ⭐⭐ |
| 字段注入(@Autowired) | 直接在字段上使用注解,简洁但可测试性差 | ⭐(不推荐) |
官方推荐:Spring 5.0+ 官方明确推荐构造器注入,因为它能保证依赖不可变、非空,避免运行时空指针异常,且便于单元测试--。
Q3:BeanFactory 和 ApplicationContext 有什么区别?
参考答案:
| 对比维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 定位 | 最底层的基础容器接口 | 增强版容器,继承 BeanFactory |
| Bean 加载时机 | 懒加载(调用 getBean() 时才创建) | 预加载(启动时创建所有单例 Bean) |
| 功能扩展 | 仅提供最核心的容器功能 | 支持国际化、事件发布、资源加载等 |
| 使用场景 | 资源受限环境 | 日常开发(SpringBoot 默认使用)-41 |
Q4:Spring 中的单例 Bean 是线程安全的吗?
参考答案:
不是。Spring 框架中的单例 Bean 默认不是线程安全的。Spring 并没有对单例 Bean 进行多线程封装处理-21。
但是:在实际开发中,如果不在单例 Bean 中定义可变的成员变量(即保持无状态),单例 Bean 也是线程安全的。一般在使用单例 Bean 时不会设置共享数据,因此实际使用时基本不存在线程安全问题-21。
Q5:Spring IoC 容器底层如何实现对象的创建?
参考答案:
Spring IoC 容器底层主要依赖 Java 反射机制 来实现对象的动态创建和依赖注入。核心流程为:
启动时扫描注解或解析配置,生成
BeanDefinition(Bean 的定义信息)将
BeanDefinition注册到BeanDefinitionRegistry通过反射调用构造器
Constructor.newInstance()创建对象实例通过反射完成依赖注入(
Field.set()或Method.invoke())执行初始化回调,将完成的 Bean 放入单例池(
singletonObjectsMap)-41
九、结尾总结
9.1 本文核心知识点回顾
| 知识点 | 核心要点 |
|---|---|
| IoC(控制反转) | 设计思想,将对象创建/管理权从代码转移到容器 |
| DI(依赖注入) | 技术手段,通过构造器/Setter/字段将依赖“送进来” |
| 两者关系 | IoC 是思想,DI 是实现,Spring 通过 DI 实现 IoC |
| 依赖注入方式 | 构造器注入(推荐)、Setter 注入、字段注入(不推荐) |
| 底层原理 | 反射机制 + 设计模式(工厂/单例/模板方法) |
| 容器接口 | BeanFactory(基础)→ ApplicationContext(增强) |
9.2 易错点提醒
❌ 混淆 IoC 和 DI:IoC 是“思想”,DI 是“动作”,不是一回事!
❌ 误以为 IoC 就是“用框架 new 对象”:IoC 的核心是“控制权转移”,而不是“谁在 new”。
❌ 随意使用字段注入(@Autowired) :官方推荐构造器注入,字段注入在 Spring Boot 3.x / 4.x 中已逐渐被警告。
❌ 手动
new对象后还在里面用@Autowired:会导致注入字段为null,出现空指针异常-2。
9.3 系列预告
本文是《Spring IoC/DI 原理》系列的第一篇。下一篇将深入讲解:
Bean 的完整生命周期(从实例化到销毁的全过程)
Bean 作用域详解(singleton、prototype、request、session)
Spring 是如何解决循环依赖的(三级缓存机制)
欢迎持续关注!
📌 本文发布于 2026 年 4 月 8 日,内容基于 Spring Framework 6.x / 7.x 版本整理。若对文章有任何疑问或建议,欢迎在评论区留言交流。
🔗 文中代码示例均可直接运行,建议读者动手实践以加深理解。
扫一扫微信交流