Spring 的 IoCDI 原理(一):控制反转 + 依赖注入,底层竟只用了“反射”?

小编头像

小编

管理员

发布于:2026年04月28日

12 阅读 · 0 评论

北京时间 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 开发中,对象之间的依赖关系通常由开发者手动管理。来看一段典型的传统代码:

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 痛点分析

这种写法的弊端非常明显:

痛点说明
高耦合UserServiceImplUserDaoImpl 强绑定。如果需要将数据源从 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 重构后的代码:

java
复制
下载
// 数据访问层:由 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 模式
依赖创建者开发者(手动 newSpring 容器
依赖管理方式硬编码配置/注解声明
耦合度高耦合低耦合
可测试性差(无法 Mock)好(可轻松替换实现)
代码可读性依赖关系隐式依赖关系显式

五、IoC vs. DI:一句话说清

很多开发者对这两个概念容易混淆,下面用一张图帮你彻底厘清:

维度IoCDI
本质设计思想实现方式
角色设计层面(What)技术层面(How)
关系思想指导手段落地
核心“控制权交给容器”“依赖由容器注入”

一句话概括:IoC 是一种设计思想——让容器接管对象的创建和管理;DI 是实现这一思想的具体手段——容器主动将依赖“注入”给对象。

打个比方:IoC 就像是“你告诉厨师你要吃什么菜”,而 DI 就是“厨师把做好的菜端到你面前”这个动作本身-16

六、代码对比:从传统到 Spring 的演进

为了让你更直观地感受 IoC/DI 带来的变化,下面分别用 XML 配置注解配置 两种方式展示 Spring 的配置演进。

6.1 XML 配置方式(早期 Spring)

xml
复制
下载
运行
<!-- 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 配置方式(主流推荐)

java
复制
下载
@Configuration
public class AppConfig {
    @Bean
    public UserDao userDao() {
        return new UserDaoImpl();
    }
    
    @Bean
    public UserService userService(UserDao userDao) {
        return new UserServiceImpl(userDao);
    }
}

6.3 注解配置方式(最简洁,日常开发首选)

java
复制
下载
@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 的底层核心实现依赖于两个关键技术:

  1. Java 反射机制:容器在运行时通过反射获取类的构造器、方法和字段信息,动态创建对象并注入依赖;

  2. 设计模式:工厂模式(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. 初始化执行初始化逻辑调用 @PostConstructInitializingBean 等初始化方法
6. 销毁执行销毁逻辑容器关闭时,调用 @PreDestroydestroy-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 的区别?

参考答案

维度BeanFactoryApplicationContext
加载时机懒加载(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 核心知识点回顾

  1. IoC(控制反转) :一种设计思想,将对象创建和依赖管理的控制权从开发者转移到容器;

  2. DI(依赖注入) :IoC 的具体实现方式,由容器主动将依赖“注入”给对象;

  3. 核心关系:IoC 是思想(What),DI 是手段(How),Spring 通过 DI 实现 IoC

  4. 底层原理:依赖 Java 反射机制 + 工厂模式 实现;

  5. 最佳实践:推荐使用构造器注入,确保依赖的不可变性和可测试性。

9.2 易错点提醒

  • ❌ 不要把 IoC 和 DI 说成“两个并列的东西”——它们是“思想”与“实现”的关系;

  • ❌ 不要忽略反射机制的重要性——这是 IoC 容器能够“动态管理对象”的技术根基;

  • ❌ 字段注入虽然方便,但会影响可测试性,生产项目中应谨慎使用。

9.3 预告

本篇是 Spring IoC/DI 原理系列的第一篇。下一篇我们将深入 Bean 的生命周期,详细拆解 Spring 容器是如何管理 Bean 从“出生”到“销毁”的全过程的,敬请期待!

📌 本文首发于 AI 复习助手,欢迎收藏、转发、点赞!

参考资料:Spring Framework 官方文档、CSDN、掘金、imooc 等社区优质文章。

标签:

相关阅读