行业资讯
HOME
行业资讯
正文内容
Spring 的 IoCDI 原理(一):读懂“控制反转”与“依赖注入”,别再只会用@Autowired了
发布时间 : 2026-04-28
作者 : 小编
访问数量 : 3
扫码分享至微信

北京时间 2026年4月8日 | 目标读者:技术入门/进阶学习者、在校学生、面试备考者、相关技术栈开发工程师

〇、写在前面:你为什么需要读完这篇?

如果你用 Spring 开发过项目,大概率写过这样的代码:

java
复制
下载
@Service

public class UserService { @Autowired private UserDao userDao; }

然后就这样用了——会用,但讲不清为什么这么写、底层发生了什么、面试被问到时只能憋出一句“IoC就是控制反转,DI就是依赖注入”

这篇文章的目标只有一个:帮你把IoC/DI这根“弹簧面试铁三角”的第一条腿,从模糊的概念变成清晰的认知链路

本文不讲晦涩理论堆砌,而是按照 “问题 → 概念 → 关系 → 示例 → 原理 → 考点” 的递进逻辑,带你一次性把IoC和DI吃透。


一、为什么需要IoC?先看一段“失控”的代码

痛点切入:传统开发中的“new地狱”

假设我们有一个用户注册的业务。初始需求是:用户注册时,把用户信息保存在本地MySQL数据库

传统写法长这样:

java
复制
下载
public class UserService {
    // 硬编码依赖:业务逻辑直接依赖具体实现类
    private UserRepository userRepository = new MySQLUserRepository();
    
    public void register(String username) {
        userRepository.save(username);
    }
}

现在需求变了:要把用户数据迁移到远程MongoDB,或者分布式用户中心

问题来了:你必须手动修改UserService的源代码,把 new MySQLUserRepository() 改成 new RemoteUserRepository()-5

随着业务发展,这类问题会越来越多:

  • A类依赖B类,B类依赖C类和D类……为了拿到A对象,你得先手动new出一整条依赖链,工作量失控;

  • 同一个重量级对象(如HttpClient)在多个类中被重复创建,无法复用,也无法集中配置超时、重试等策略;-5

  • 做单元测试时,无法用Mock对象替换真实依赖

  • 需求变更时,改一处代码、重编译、重新部署——每一次改动都像在拆地雷。-15

痛点一句话总结:业务代码与具体实现类“焊死”在一起,耦合高、扩展性差、维护困难、测试痛苦。

核心矛盾:代码想要“解耦”,但new关键字在“焊死”

传统开发中,对象什么时候new、在哪里new、怎么new,全由开发者自己决定。你拿到一个A对象,如果它依赖B、C、D……你可能需要额外创建一整个对象网络。-15

这时候你会发现:代码不是在解决问题,而是在管理对象的“血缘关系”

那么有没有办法,让业务类只依赖抽象接口,而不依赖具体实现类呢?

这就是IoC要解决的问题。


二、核心概念(一):IoC(控制反转)

定义

IoC(Inversion of Control,控制反转) 是一种设计思想。其核心是:对象的创建与依赖关系的管理,不再由程序代码主动控制,而是交给外部容器来完成。-2

翻译成人话就是:把“new对象的权力”从程序员手里交出去。

一句话理解

传统方式:你想要对象?自己new一个。

IoC方式:你想要对象?告诉容器你需要什么,容器给你送过来。

这背后遵循的是著名的 “好莱坞原则” ——“Don‘t call me, I’ll call you.”(别找我们,我们会找你。)-15

类比:餐厅点餐 vs 自己买菜做饭

  • 传统开发 = 你自己去菜市场买菜、洗菜、切菜、炒菜,再端上桌。你要吃一道菜,得先搞定一整条供应链。

  • IoC模式 = 你坐在餐厅里,告诉服务员“我要一份宫保鸡丁”。你不需要关心鸡是谁杀的、花生是谁剥的、火候怎么控制——餐厅后厨(IoC容器)帮你搞定一切

IoC就是这套“餐厅后厨”的设计思想:把“做菜”这件事从你手里拿走了。


三、核心概念(二):DI(依赖注入)

定义

DI(Dependency Injection,依赖注入) 是一种设计模式,是IoC的具体实现方式。由容器动态地将依赖关系注入到对象中。-15

通俗理解

IoC说的是“我不管创建了,让别人管”——这是思想

DI说的是“别人怎么把对象给我”——这是具体做法

打个比方:

  • IoC 就像你说:“我不想自己做饭了,交给餐厅。”

  • DI 就像服务员把做好的菜端到你面前——这就是“注入”的过程

DI的三种实现方式

Spring提供了三种主要的依赖注入方式:-15

注入方式示例特点
构造器注入(推荐)public UserService(UserDao userDao) { this.userDao = userDao; }依赖不可变,便于单元测试,Spring官方首选
Setter注入@Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; }可选依赖、可重新配置
字段注入@Autowired private UserDao userDao;写法最简洁,但可测试性较差,不推荐生产环境大量使用

为什么构造器注入是官方推荐?因为构造器保证了依赖对象在对象创建时就必须存在(不可为null),并且字段可以用final修饰,天然支持不可变性和线程安全。-15


四、IoC与DI的关系:思想 vs 实现

这是面试中最常被问倒的问题,一定要记清楚

维度IoC(控制反转)DI(依赖注入)
性质设计思想 / 设计原则设计模式 / 具体实现技术
角色提出“为什么要这样做”回答“具体怎么做到的”
关注点控制权的转移依赖对象的传递方式
关系目标是IoC,手段是DI是实现IoC的具体方式之一

一句话概括:IoC是一种“思想”,DI是实现这种“思想”的“技术手段”。 -22

⚠️ 常见误区

  • ❌ 误以为IoC和DI是“两个不同的东西”——其实它们是对同一件事情的不同角度描述

  • ❌ 误以为IoC就是Spring特有的——IoC是一种通用设计思想,在其他语言/框架中也有体现;

  • ❌ 答不出“IoC和DI有什么关系”——面试官最爱挖坑的地方,请务必掌握上面的对比表格。


五、代码示例:从“手动new”到“容器注入”的完整演变

场景:UserService 依赖 UserDao

❌ 传统开发(高耦合)

java
复制
下载
// UserDao接口
public interface UserDao {
    void queryUser();
}

// UserDao实现类
public class UserDaoImpl implements UserDao {
    @Override
    public void queryUser() {
        System.out.println("查询用户信息");
    }
}

// UserService:手动new依赖对象
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();
    }
}

弊端:UserServiceImpl与UserDaoImpl强绑定,替换UserDao的实现(如从MySQL切换到Oracle)必须修改UserServiceImpl代码。-6


✅ IoC/DI模式(低耦合)

java
复制
下载
// UserDao:交给容器管理
@Repository  // 声明这是一个Bean,交给IoC容器管理
public class UserDaoImpl implements UserDao {
    @Override
    public void queryUser() {
        System.out.println("查询用户信息");
    }
}

// UserService:依赖由容器注入
@Service  // 声明这是一个Bean
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();
    }
}

核心变化:控制权从开发者转移到Spring容器——对象的创建、依赖的装配、生命周期的管理,全由容器负责。开发者只需声明“我需要什么依赖”,容器就会自动将依赖“送上门”。-6

对比总结

对比维度传统开发IoC/DI模式
对象创建开发者手动newSpring容器自动创建
依赖关系硬编码在代码中通过配置/注解声明
耦合度高(与具体实现类绑定)低(仅依赖抽象接口)
可测试性差(无法Mock)好(可注入Mock对象)
扩展性改需求需改源码替换实现类无需改业务代码

六、底层原理:到底是怎么做到的?(反射)

这里需要稍微深入一点。理解了这一层,你的面试答案就能从“背概念”升级为“讲原理”

IoC容器启动的核心流程

Spring IoC容器的启动,本质上做了三件事:-1

步骤1:加载配置元数据

  • 容器扫描被@Component@Service@Repository@Controller等注解标记的类;

  • 将扫描到的类封装为 BeanDefinition(Bean定义对象)——相当于“Bean的说明书”,包含了类名、是否单例、依赖关系、初始化方法等信息。

步骤2:注册BeanDefinition到容器

  • 容器将BeanDefinition注册到注册表中,本质是一个 Map<String, BeanDefinition>(key是Bean名称,value是Bean定义)。

步骤3:Bean的实例化与依赖注入(核心)

  • 容器根据BeanDefinition创建Bean对象,并完成依赖注入;

  • 这一过程依赖的核心技术就是——Java反射(Reflection)。

反射在这里做了什么?

Java反射允许程序在运行时动态地获取类的信息、动态创建对象、动态调用方法、动态访问/修改属性。

Spring底层正是利用反射来实现DI的:-

  • 实例化:容器通过反射调用类的构造器来创建对象实例(Constructor.newInstance());

  • 依赖注入:容器通过反射获取类中被@Autowired标记的字段或方法,然后动态地将依赖对象赋值进去(Field.set()Method.invoke())。

简化版逻辑:

java
复制
下载
// Spring底层简化示意(非真实源码)
// 1. 通过反射创建对象实例
Class<?> clazz = Class.forName("com.example.UserServiceImpl");
Constructor<?> constructor = clazz.getDeclaredConstructor();
Object instance = constructor.newInstance();

// 2. 通过反射注入依赖字段
Field field = clazz.getDeclaredField("userDao");
field.setAccessible(true);  // 突破private限制
field.set(instance, userDaoInstance);  // 注入依赖对象

一句话总结底层逻辑:

IoC是设计思想,DI是实现方式,而反射是支撑DI得以实现的技术手段。 -5-

扩展:IoC容器的两大核心接口

接口定位加载时机日常使用场景
BeanFactory最基础的IoC容器接口懒加载(调用getBean()时才创建)嵌入式系统、资源受限环境
ApplicationContext增强版容器,继承自BeanFactory预加载(启动时创建所有单例Bean)绝大多数企业项目(推荐)

常见实现类

  • AnnotationConfigApplicationContext:基于注解配置(最常用)

  • ClassPathXmlApplicationContext:基于XML配置-2

面试加分点:能说出ApplicationContext在启动时会执行refresh()方法,触发配置扫描→Bean定义注册→实例化→初始化的完整流程。-2


七、高频面试题与参考答案

面试题1:什么是IoC?什么是DI?它们之间有什么关系?

参考答案(三层递进结构):

第一层(定义) :IoC(Inversion of Control,控制反转)是一种设计思想,将对象的创建和依赖管理权从代码内部“反转”给外部容器。DI(Dependency Injection,依赖注入)是一种设计模式,是IoC的具体实现方式。

第二层(关系)IoC是目标,DI是手段。IoC回答“为什么要这样做”(解耦),DI回答“具体怎么做的”(通过构造器、Setter或字段将依赖注入)。

第三层(本质) :它们是对同一件事情的不同角度描述——都是指Spring容器接管了对象的创建和依赖管理,开发者只需声明“需要什么”,无需关心“如何获取”。

面试题2:Spring IoC容器是如何实现依赖注入的?底层用了什么技术?

参考答案:

Spring底层主要依赖Java反射机制来实现依赖注入。流程如下:

  1. 容器启动时,扫描带@Component等注解的类,将其封装为BeanDefinition(Bean的“说明书”);

  2. 容器通过反射调用构造器创建对象实例;

  3. 容器通过反射扫描字段或方法上的@Autowired注解,将匹配的依赖对象动态注入进去。

一句话概括:IoC是思想,DI是方式,反射是支撑这一切的技术基础。

面试题3:@Autowired@Resource有什么区别?

参考答案:

维度@Autowired@Resource
来源Spring框架自带JDK原生(JSR-250规范)
默认匹配策略按类型(byType)按名称(byName)
指定名称配合@Qualifier("beanName")直接使用@Resource(name="beanName")
适用场景Spring项目(推荐)需要与Java EE规范兼容时

如果容器中存在多个同类型的Bean,使用@Autowired会报错,此时需要用@Qualifier@Primary解决。-37

面试题4:BeanFactory和ApplicationContext的区别?

参考答案:

  • BeanFactory是Spring IoC容器的最基础接口,采用懒加载(调用getBean()时才创建Bean),轻量但功能有限。

  • ApplicationContext继承自BeanFactory,采用预加载(容器启动时创建所有单例Bean),扩展了国际化、事件发布、AOP等企业级功能,是日常开发的首选

一句话:ApplicationContext是BeanFactory的超集,除非资源极度受限,否则都用ApplicationContext。

面试题5:构造器注入、Setter注入、字段注入,哪种方式最好?为什么?

参考答案:

推荐使用构造器注入,原因如下:

  1. 依赖不可变:依赖字段可声明为final,保证对象创建后依赖不会被修改;

  2. 防止空指针:构造器注入强制依赖在对象实例化时就必须存在,避免@Autowired字段为null的风险;

  3. 便于单元测试:可以通过构造器直接传入Mock对象,无需启动Spring容器;

  4. Spring官方推荐:在Spring 4.x之后的官方文档中,构造器注入被列为首选方式。

字段注入虽然代码简洁,但可测试性差、无法保证依赖不可变,不推荐在核心业务代码中大量使用。


八、结尾总结

核心知识点回顾

  1. IoC(控制反转) :一种设计思想,核心是“把对象的创建权从代码交给容器”,目的是解耦

  2. DI(依赖注入) :实现IoC的具体技术手段,Spring通过构造器、Setter、字段三种方式将依赖注入到对象中。

  3. IoC与DI的关系思想 vs 实现——IoC是“目标”,DI是“手段”。

  4. 底层原理:Spring依赖Java反射机制,在运行时动态创建对象、注入依赖。

  5. 两大容器接口BeanFactory(基础,懒加载)和 ApplicationContext(增强,预加载)。

  6. 推荐注入方式:构造器注入 > Setter注入 > 字段注入。

重要提醒

IoC和DI不是两个“不同的东西”,而是同一件事的两个角度。 面试时如果被问到区别,要能说清楚“IoC是思想,DI是实现”这个层次关系,而不是简单说“它们一样”或“它们不同”。

下篇预告

下一篇将深入讲解:

  • Spring IoC容器的完整生命周期(从实例化到销毁)

  • BeanPostProcessor扩展点机制

  • 循环依赖的解决方案(三级缓存原理)

  • 结合源码进一步理解refresh()方法的执行流程


📍 本文核心金句(建议收藏记忆):

IoC 是设计思想,DI 是实现方式,反射是技术手段。Spring把这三者组合在一起,才有了我们今天“声明一下就能用”的开发体验。


📌 版权声明:本文基于技术公开资料整理,旨在帮助开发者系统学习Spring IoC/DI核心知识。欢迎转发分享,请保留原文出处。

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

QQ

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

热线

188-0000-0000
专属服务热线

微信

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