传统Java开发中,每天都在写new对象、手动维护依赖关系的代码,改一个实现类就要到处改调用方,单元测试更是寸步难行。这正是Spring框架
一、痛点切入:传统开发的“new”地狱

先看一段典型的传统代码:
// 传统开发方式——紧耦合public class OrderService { private PaymentService payment = new AlipayService(); private Logger logger = new FileLogger("/tmp/log"); void pay() { payment.process(); // 想换成微信支付?改代码重编译! } }
这段代码暴露了三个核心问题:
更可怕的是连锁依赖:想要用对象A,发现A依赖B,B又依赖C,为了拿到A,不得不先手动创建C和B——工作量像雪球一样越滚越大-2。
那出路在哪?其实只需要把new对象的权力“外包”出去。需要什么,直接找第三方要就行了——这就是控制反转(IoC)思想的雏形-2。
二、IoC(控制反转):把对象的“生杀大权”交出去
定义:IoC全称 Inversion of Control(控制反转),是一种设计思想。它将传统上由程序代码直接操控的对象创建和调用权,交给外部容器来统一管理-14。
拆解一下关键词:
“反转” :反的是传统模式下“自己new对象、自己管理依赖”的控制流。
“控制权转移” :从开发者手里转移到Spring IoC容器手中。
生活化类比:好比一位餐厅厨师长,以前每道菜都要亲自去市场买菜、备菜、炒菜,忙得不可开交。现在餐厅请了一位“大管家”(IoC容器),厨师长只负责炒菜(业务逻辑),买菜备菜全交给管家——这就是控制反转-24。
IoC的价值:不只是少写几行new代码,而是实现了彻底解耦。依赖关系通过配置文件或注解外置,组件之间只依赖接口,不依赖具体实现-。
三、DI(依赖注入):IoC思想的“落地手段”
定义:DI全称 Dependency Injection(依赖注入),是一种设计模式,也是IoC思想的具体实现方式。由容器动态地将依赖关系注入到对象中-2。
与IoC的关系:IoC是“让别人帮你统筹安排”的思想,DI是“别人具体帮你送东西”的动作——二者是思想与实现的关系-18。
一句话记住:IoC是设计思想,DI是落地手段;Spring通过DI来实现IoC-。
Spring支持三种依赖注入方式-2:
| 注入方式 | 写法示例 | 优点 | 缺点 |
|---|---|---|---|
| 构造器注入(官方推荐) | public UserService(UserDao dao) { this.dao = dao; } | 依赖不可变、不能为空、安全性最高;解决循环依赖 | 依赖多时构造函数参数臃肿 |
| Setter注入 | @Autowired public void setDao(UserDao dao) { this.dao = dao; } | 灵活,支持可选依赖 | 对象创建后可被修改,存在安全风险 |
| 字段注入(常用但不推荐) | @Autowired private UserDao dao; | 代码简洁 | 无法注入final变量、单元测试困难、耦合度高 |
四、传统 vs Spring IoC:代码对比
传统方式(紧耦合):
public class UserService { private EmailService emailService = new EmailService(); // 直接创建依赖 public void register(User user) { emailService.sendConfirmationEmail(user); } }
Spring IoC + DI方式(松耦合):
@Service public class UserService { @Autowired // 声明需要什么,容器负责注入 private EmailService emailService; public void register(User user) { emailService.sendConfirmationEmail(user); } }
关键变化一目了然:UserService不再关心EmailService怎么创建,只管使用。要换邮件服务商?只需替换实现类配置,UserService的代码一个字都不用改-24。
五、底层原理:IoC容器是如何工作的?
IoC容器底层依赖三个关键技术支柱:
反射机制:容器通过反射调用类的构造方法创建Bean实例,在运行时动态获取类的元信息并进行实例化-14。
工厂模式 + 容器缓存:Spring IoC容器本质是一个超级工厂(BeanFactory),管理着所有Bean的单例缓存,避免了重复创建。
代理机制(AOP相关) :为Bean生成动态代理对象,实现事务管理、日志切面等横切关注点。
核心流程:Spring启动时,通过组件扫描(@ComponentScan)找到所有标记了@Component、@Service、@Controller等注解的类,利用反射实例化后存入容器(默认是单例),再根据@Autowired等注解解析依赖关系并注入-5。执行流程可概括为:组件扫描 → Bean实例化 → 依赖注入 → 初始化 → 就绪使用。
六、高频面试题与参考答案
问题1:谈谈你对Spring IoC和DI的理解?它们的关系是什么?
参考答案:IoC(Inversion of Control,控制反转)是一种设计思想,它将对象的创建、依赖管理的控制权从程序代码本身转移到外部容器。DI(Dependency Injection,依赖注入)是IoC的具体实现方式,指容器在创建对象时自动将依赖对象注入进来。IoC是思想,DI是手段,Spring通过DI来实现IoC。
踩分点:IoC定义、DI定义、二者关系(思想vs实现)--18。
问题2:Spring DI有哪几种注入方式?你推荐哪一种?
参考答案:有三种:构造器注入、Setter注入、字段注入。推荐构造器注入——因为它保证了依赖的不可变性和非空性,解决了循环依赖问题,且最符合依赖倒置原则。生产环境优先用构造器注入,可选依赖用Setter注入,尽量避免字段注入。
踩分点:列出三种方式、推荐构造器注入并给出理由-67。
问题3:IoC容器中的Bean默认是什么作用域?是线程安全的吗?
参考答案:默认是singleton(单例),即整个IoC容器中只有一个实例。默认不是线程安全的——Spring没有对单例Bean做多线程封装。但大多数情况下(Controller、Service、Dao无状态),单例Bean本身就是线程安全的。如果有状态,需要开发者自行保证线程安全,或设置为prototype作用域。
踩分点:默认singleton、线程安全问题的本质、无状态Bean天然安全、有状态的解决方案-5-11。
问题4:BeanFactory和ApplicationContext有什么区别?
参考答案:ApplicationContext继承自BeanFactory。核心区别:BeanFactory采用延迟加载,第一次getBean时才初始化;ApplicationContext在容器启动时就完成所有Bean的初始化(预加载)。ApplicationContext提供了国际化、事件广播等企业级功能,是开发中的首选。
踩分点:继承关系、加载时机差异、功能扩展-46。
七、总结
回顾全文核心要点:
| 核心概念 | 一句话总结 |
|---|---|
| IoC(控制反转) | 一种设计思想,把对象的创建和管理权交给容器 |
| DI(依赖注入) | IoC的具体实现方式,容器把依赖“送”给对象 |
| 二者关系 | IoC是思想,DI是手段,Spring通过DI实现IoC |
| 底层依赖 | 反射、工厂模式、动态代理 |
| 三种注入方式 | 构造器注入(推荐)、Setter注入、字段注入(慎用) |
| Bean默认作用域 | singleton(单例),非线程安全但有状态时才需要关注 |
易错点提醒:不要将IoC和DI混为一谈——IoC是设计思想,DI是落地手段;字段注入虽然写起来爽,但会引入final约束、空指针和测试困难等问题,生产环境慎用。
掌握了IoC与DI,就抓住了Spring框架的灵魂。下一篇将深入Spring AOP(面向切面编程),聊聊如何无侵入地实现日志、事务等横切关注点,敬请期待。
🔍 想获取更多技术资料?试试文献AI助手,专业文献、整理技术笔记,助你学习效率翻倍。
