智能制造
HOME
智能制造
正文内容
🔄 Spring 的 IoCDI 原理(一):从“主动 new”到“容器接管”的底层变革
发布时间 : 2026-05-13
作者 : 小编
访问数量 : 12
扫码分享至微信

本文导读:本文面向希望从“会用 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 开发中,对象之间的依赖关系通常由开发者手动管理。看下面这段代码:

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 传统方式的四大弊端

上述代码虽然能正常运行,但存在严重的架构问题:

弊端具体表现
高耦合UserServiceImplUserDaoImpl 强绑定,若要切换实现(如从 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 注解配置示例(构造器注入)

java
复制
下载
// 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 核心结论

维度IoCDI
定位设计思想 / 设计原则技术手段 / 落地方式
回答的问题“谁来管理对象的创建?”“依赖如何传递进去?”
比喻思想:“让别人帮你统筹安排”动作:“别人具体帮你送东西”-25

5.2 一句话总结

IoC 是“想通了要把控制权交出去”的设计思想,DI 是“具体怎么交出去”的技术实现。

5.3 对比强化记忆

text
复制
下载
┌─────────────────────────────────────────────────────────┐
│  IoC(控制反转):对象的管理权归容器,开发者只管用       │
│                          ↓ 通过                          │
│  DI(依赖注入):把依赖对象“注入”到需要它的对象中        │
│                    (构造器 / Setter / 字段)            │
└─────────────────────────────────────────────────────────┘

在 Spring 框架中,DI 是实现 IoC 的唯一方式-12。两者不可分割:没有 DI,IoC 无法落地;没有 IoC 思想,DI 就只是一套注入机制。


六、代码示例:从“手动管理”到“容器接管”

6.1 IoC 模式下的代码

java
复制
下载
// 依赖对象: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

text
复制
下载
步骤 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 中的体现
工厂模式BeanFactoryApplicationContext 作为对象工厂,统一创建和管理 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 有什么区别?

参考答案

对比维度BeanFactoryApplicationContext
定位最底层的基础容器接口增强版容器,继承 BeanFactory
Bean 加载时机懒加载(调用 getBean() 时才创建)预加载(启动时创建所有单例 Bean)
功能扩展仅提供最核心的容器功能支持国际化、事件发布、资源加载等
使用场景资源受限环境日常开发(SpringBoot 默认使用)-41

Q4:Spring 中的单例 Bean 是线程安全的吗?

参考答案

不是。Spring 框架中的单例 Bean 默认不是线程安全的。Spring 并没有对单例 Bean 进行多线程封装处理-21

但是:在实际开发中,如果不在单例 Bean 中定义可变的成员变量(即保持无状态),单例 Bean 也是线程安全的。一般在使用单例 Bean 时不会设置共享数据,因此实际使用时基本不存在线程安全问题-21


Q5:Spring IoC 容器底层如何实现对象的创建?

参考答案

Spring IoC 容器底层主要依赖 Java 反射机制 来实现对象的动态创建和依赖注入。核心流程为:

  1. 启动时扫描注解或解析配置,生成 BeanDefinition(Bean 的定义信息)

  2. BeanDefinition 注册到 BeanDefinitionRegistry

  3. 通过反射调用构造器 Constructor.newInstance() 创建对象实例

  4. 通过反射完成依赖注入(Field.set()Method.invoke()

  5. 执行初始化回调,将完成的 Bean 放入单例池(singletonObjects Map)-41


九、结尾总结

9.1 本文核心知识点回顾

知识点核心要点
IoC(控制反转)设计思想,将对象创建/管理权从代码转移到容器
DI(依赖注入)技术手段,通过构造器/Setter/字段将依赖“送进来”
两者关系IoC 是思想,DI 是实现,Spring 通过 DI 实现 IoC
依赖注入方式构造器注入(推荐)、Setter 注入、字段注入(不推荐)
底层原理反射机制 + 设计模式(工厂/单例/模板方法)
容器接口BeanFactory(基础)→ ApplicationContext(增强)

9.2 易错点提醒

  1. 混淆 IoC 和 DI:IoC 是“思想”,DI 是“动作”,不是一回事!

  2. 误以为 IoC 就是“用框架 new 对象”:IoC 的核心是“控制权转移”,而不是“谁在 new”。

  3. 随意使用字段注入(@Autowired) :官方推荐构造器注入,字段注入在 Spring Boot 3.x / 4.x 中已逐渐被警告。

  4. 手动 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 版本整理。若对文章有任何疑问或建议,欢迎在评论区留言交流。


🔗 文中代码示例均可直接运行,建议读者动手实践以加深理解。

王经理: 180-0000-0000(微信同号)
10086@qq.com
北京海淀区西三旗街道国际大厦08A座
©2026  上海羊羽卓进出口贸易有限公司  版权所有.All Rights Reserved.  |  程序由Z-BlogPHP强力驱动
网站首页
电话咨询
微信号

QQ

在线咨询真诚为您提供专业解答服务

热线

188-0000-0000
专属服务热线

微信

二维码扫一扫微信交流
顶部