本文基于 2026-04-10 的最新资料,深入解析 Spring 的两大核心机制——IoC 与 DI。
开篇引入

Spring 框架自诞生以来,始终以“轻量级、非侵入、面向接口编程”为信条,在 Java 企业级开发中占据不可撼动的地位。而这一切的根基,正是两个看似朴素却极具颠覆性的思想:IoC(控制反转)与 DI(依赖注入)。前者将对象创建的权力从程序员手中交给容器,后者则是实现这一转变的具体手段-5。
不少开发者对 IoC 与 DI 的认知停留在“会用 @Autowired”层面,一旦被问到“两者有什么区别”“Spring 如何解决循环依赖”,便无从答起。这恰恰是面试中高频出现的扣分点。

本文将从为什么要用 → 概念解析 → 代码示例 → 底层原理 → 面试要点这条主线展开,带你一次性彻底搞懂 Spring IoC 与 DI。
一、痛点切入:为什么需要 IoC?
传统开发方式
在传统开发中,当我们需要使用一个对象时,直接在代码里用 new 关键字创建它:
public class OrderService { // 硬编码依赖 private PaymentService payment = new AlipayService(); private Logger logger = new FileLogger("/tmp/log"); void pay() { payment.process(); } }
传统方式的致命缺陷
紧耦合:想将支付宝换成微信支付,必须修改
OrderService源代码,重新编译部署;难以测试:无法轻松替换为 Mock 对象进行单元测试;
维护成本高:一个对象可能依赖另一个对象,后者又依赖第三个……为了拿到对象 A,可能要手动创建一串依赖链-9。
IoC 的设计初衷
为了打破这种困局,软件设计领域提出了 控制反转(IoC) 这一设计原则。IoC 的核心思想是:将对象创建、管理的控制权从应用程序代码转移给外部容器,由容器统一调度、管理与销毁-5。
二、IoC:控制反转
标准定义
IoC(Inversion of Control,控制反转) 是一种设计思想,指的是将原本在程序中手动创建对象的控制权,交给第三方(如 IoC 容器)来管理-29。
核心拆解
控制:对象创建(实例化)、生命周期管理、依赖管理的权力;
反转:这种权力从程序员代码移交给了外部容器-29。
生活化类比
传统方式:就像自己在家做饭。你需要主动去超市买菜、洗菜、切菜、炒菜,整个过程完全由你控制-1。
IoC 方式:就像去餐厅吃饭。你只需要点菜(声明需要什么),厨师(IoC 容器)负责把菜做好送到你面前-1。你不需要关心菜是怎么做的,只管享用。
IoC 解决了什么问题?
降低耦合度:对象之间不再直接依赖,而是依赖容器;
资源易于管理:如单例、作用域控制由容器统一处理;
提升可测试性:依赖可以被轻松替换为 Mock 对象-29。
三、DI:依赖注入
标准定义
DI(Dependency Injection,依赖注入) 是一种设计模式,是 IoC 的具体实现方式,由容器动态地将依赖关系注入到对象中-9。
DI 的三种实现方式
Spring 主要提供了三种依赖注入方式:
| 注入方式 | 实现方式 | 推荐度 | 关键特征 |
|---|---|---|---|
| 构造器注入 | 通过构造函数参数注入 | ✅ 最推荐 | 依赖不可变、强制注入 |
| Setter 注入 | 通过 Setter 方法注入 | 可选场景 | 适合可选依赖 |
| 字段注入 | 通过 @Autowired 直接注入字段 | ⚠️ 不推荐生产 | 写法最简洁但易出 NPE |
构造器注入(推荐方式) :Spring 官方首选,依赖声明为 final,确保对象一旦创建就拥有完整依赖,避免运行时 NPE-9-23。
@Service public class UserService { private final UserRepository userRepository; // 构造器注入 - 推荐 public UserService(UserRepository userRepository) { this.userRepository = userRepository; } }
Setter 注入:适合可选依赖,允许对象创建后动态修改依赖-23。
@Service public class UserService { private UserRepository userRepository; @Autowired // 可选:也可以不加 @Autowired,Spring 4.3+ 自动推断 public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } }
字段注入:写法最简洁,但框架侵入性强,Spring 官方不推荐用于生产环境-23。
@Service public class UserService { @Autowired // 不推荐:与 Spring 框架强耦合,易出现 NPE private UserRepository userRepository; }
四、IoC 与 DI 的关系与区别
两者关系一句话总结
IoC 是设计思想,DI 是实现手段;IoC 回答“谁来控制”,DI 回答“如何传递”。
详细对比
| 维度 | IoC(控制反转) | DI(依赖注入) |
|---|---|---|
| 本质 | 设计原则、架构思想 | 具体的设计模式、实现技术 |
| 范畴 | 宽泛,涵盖程序流程控制 | 具体,专注于对象依赖管理 |
| 回答的问题 | “谁来控制?” | “怎么传递依赖?” |
| 实现方式 | DI、服务定位器、模板方法等 | 构造器注入、Setter 注入、字段注入 |
关键辨析
一个系统可以存在 IoC 但不使用 DI,例如通过 JNDI 查找服务,控制权已交予容器,但未发生“注入”动作-2;
当使用依赖注入时,一定已经在应用 IoC 的原则,因为注入行为由外部容器驱动-1;
若仅在类内部调用另一个类的 Setter 方法并传入实例,该行为本身不是 DI,因为控制权仍在当前类,并非由外部容器驱动-2。
五、代码示例:从混乱到优雅
传统方式(高耦合)
public class OrderService { private PaymentService payment = new AlipayService(); // 硬编码 public void pay() { payment.process(); // 想换成微信支付?改代码重编译! } }
问题:OrderService 直接依赖 AlipayService 的具体实现,换支付方式必须改源码。
IoC + DI 方式(低耦合)
@Service public class OrderService { private final PaymentService payment; // 依赖接口,不依赖具体实现 @Autowired // 构造器注入(Spring 4.3+ 可省略此注解) public OrderService(PaymentService payment) { this.payment = payment; } public void pay() { payment.process(); // 具体是支付宝还是微信,由配置决定 } }
改进效果:
OrderService只依赖PaymentService接口,不关心具体实现;更换支付方式只需修改配置(如
@Primary或@Qualifier),无需改动业务代码;单元测试时可轻松传入 Mock 对象。
六、底层原理:IoC 容器如何工作?
技术支撑
Spring IoC 的底层实现主要依赖两大技术:
反射(Reflection) :在运行时动态获取类的构造器、方法和属性,实现对象的实例化与依赖注入;
设计模式:工厂模式(Bean 的创建与获取)、模板方法模式(Bean 生命周期控制)等--11。
IoC 容器核心流程
以最常用的注解配置为例,Spring 容器从启动到创建 Bean 的完整流程如下:
┌─────────────────────────────────────────────────────────────┐ │ Step 1: 容器初始化 → 扫描包路径,解析 @Component 注解 │ │ ↓ │ │ Step 2: 封装 BeanDefinition → 生成 Bean 的“说明书” │ │ ↓ │ │ Step 3: 注册到 BeanDefinitionRegistry → 存入 Map │ │ ↓ │ │ Step 4: 实例化 Bean → 通过反射调用构造器创建对象实例 │ │ ↓ │ │ Step 5: 依赖注入 → 为实例填充 @Autowired 标注的属性 │ │ ↓ │ │ Step 6: 执行初始化回调 → @PostConstruct、InitializingBean │ │ ↓ │ │ Step 7: Bean 就绪 → 存入单例池(singletonObjects),供业务使用 │ └─────────────────────────────────────────────────────────────┘
BeanDefinition 是关键——它包含了 Bean 的所有信息:类名、作用域、依赖关系、初始化方法等,相当于 Bean 的“说明书”-11。
核心接口:BeanFactory 与 ApplicationContext
BeanFactory:最基础的 IoC 容器接口,提供
getBean()等核心能力,采用懒加载模式,调用时才创建 Bean;ApplicationContext:日常开发使用的增强版容器,继承 BeanFactory,在启动时即创建所有单例 Bean,并额外支持国际化、事件发布等企业级功能-11。
七、进阶话题:Spring 如何解决循环依赖?
什么是循环依赖?
循环依赖指两个或多个 Bean 之间互相持有对方的引用,形成闭环。最典型的场景:Bean A 依赖 Bean B,Bean B 又依赖 Bean A-36。
@Component class A { @Autowired private B b; // A 依赖 B } @Component class B { @Autowired private A a; // B 依赖 A,形成循环 }
Spring 的解决方案:三级缓存
为了解决单例 Bean 的循环依赖问题,Spring 设计了 三级缓存机制,通过提前暴露半成品 Bean 的方式打破依赖闭环-36。
| 缓存级别 | 缓存名称 | 作用 |
|---|---|---|
| 一级缓存 | singletonObjects | 存放完全初始化完成的成品 Bean,供业务直接使用 |
| 二级缓存 | earlySingletonObjects | 存放提前暴露的半成品 Bean(已实例化但未完成依赖注入) |
| 三级缓存 | singletonFactories | 存放 ObjectFactory 工厂对象,用于生成早期引用 |
为什么需要三级而非二级? 因为 Spring 需要兼顾两个需求:保证循环依赖能解,同时保证 AOP 代理生效。如果只有二级缓存,就得在 Bean 创建实例后立刻决定是否生成代理,但此时尚未走到初始化步骤,无法判断是否需要增强。三级缓存将“是否生成代理”延迟到第一次被其他 Bean 引用时再决定,实现按需代理-37。
注意:Spring 的三级缓存仅能解决单例 + Setter/字段注入场景下的循环依赖。构造器注入的循环依赖无法解决,会直接抛出异常。
八、高频面试题与参考答案
面试题 1:什么是 Spring 的 IoC?有什么作用?
标准回答:
IoC(Inversion of Control,控制反转)是一种设计思想,指的是将对象的创建、依赖关系的管理和生命周期的控制从程序本身转移给 Spring 容器。开发者只需要声明依赖关系,不需要手动创建对象。IoC 的主要作用是降低代码耦合度、提升可测试性和可维护性。
踩分点:对象创建权移交、解耦、容器管理
面试题 2:IoC 和 DI 有什么区别?
标准回答:
IoC 是一种设计思想,DI 是 IoC 的具体实现方式。IoC 回答的是“谁来控制”的问题,即控制权从程序移交给了容器;DI 回答的是“怎么传递”的问题,即通过构造器注入、Setter 注入或字段注入等方式将依赖传递给对象。没有 DI,IoC 无法落地;没有 IoC,DI 只是普通的方法传参。
踩分点:思想 vs 实现、不同维度、不可互换
面试题 3:Spring 的依赖注入有哪几种方式?推荐哪种?
标准回答:
Spring 提供三种主要的依赖注入方式:构造器注入、Setter 注入和字段注入。构造器注入是官方最推荐的方式,因为它能保证依赖不可变、强制注入、便于单元测试。字段注入写法最简洁,但框架侵入性强、易出现 NPE,不推荐在生产环境中使用。
踩分点:三种方式对比、构造器注入的优点、字段注入的缺点
面试题 4:Spring 如何解决循环依赖问题?
标准回答:
Spring 通过三级缓存机制来解决单例 Bean 的循环依赖问题。三级缓存分别是:一级缓存 singletonObjects(存成品 Bean)、二级缓存 earlySingletonObjects(存半成品 Bean)、三级缓存 singletonFactories(存 ObjectFactory 工厂)。核心原理是提前暴露正在创建中的 Bean——当一个 Bean 实例化后但还未完成依赖注入时,就将它提前放入三级缓存,这样其他 Bean 在依赖它时可以直接获取到引用,从而打破依赖闭环。三级缓存同时兼顾了循环依赖破局和 AOP 代理的正确生成。
踩分点:三级缓存的名称和作用、提前暴露、AOP 代理场景
九、结尾总结
核心知识点回顾
IoC 是设计思想:将对象创建和依赖管理的控制权从程序代码移交给容器;
DI 是实现手段:通过构造器注入、Setter 注入、字段注入实现 IoC;
二者关系:IoC 回答“谁来控制”,DI 回答“如何传递”,本质不同但紧密配合;
底层原理:Spring 依赖反射 + 设计模式实现 IoC 容器;
循环依赖:Spring 通过三级缓存解决单例 Bean 的循环依赖问题,核心是提前暴露;
注入方式推荐:构造器注入 > Setter 注入 > 字段注入。
重点提示
⭐ 面试中常被问“IoC 和 DI 的关系”,记住核心公式:IoC = 思想,DI = 实现方式;
⭐ 字段注入方便但易出问题,构造器注入才是生产环境首选;
⭐ 三级缓存的名字和作用是高频考点,建议熟记。
下篇预告
本系列下一篇文章将深入解析 Spring 的另一核心机制——AOP(面向切面编程),从动态代理到底层实现,结合代码示例讲透 Spring 如何实现声明式事务管理,欢迎持续关注。