# Spring 中的 DI 是什么?
Table of Contents
Spring 中的 DI 是什么?
Spring 中的 DI(依赖注入)面试标准答案
【核心定义】
DI(Dependency Injection,依赖注入)是 Spring 框架实现 IoC(控制反转) 的核心机制,其本质是将对象依赖关系的创建和管理从程序内部转移到外部容器,通过容器在运行时动态地将依赖对象注入到目标对象中。
【关键要点】
-
控制反转(IoC)的实现方式
DI 是 IoC 的具体技术实现。传统编程中,对象主动创建和管理其依赖;在 DI 模式下,容器负责创建并注入依赖,控制权发生反转。 -
三种主要注入方式
- 构造器注入:通过构造函数参数注入依赖,强制依赖完整,利于不可变对象和线程安全。
- Setter 注入:通过 setter 方法注入,灵活性高,支持可选依赖。
- 字段注入:通过
@Autowired直接注入字段,代码简洁但隐藏依赖关系,不推荐用于强制依赖。
-
依赖查找 vs 依赖注入
DI 是容器主动推送依赖到对象;依赖查找(如BeanFactory.getBean())是对象主动向容器索取依赖。Spring 主要倡导 DI,更符合松耦合原则。 -
@Autowired 注解的工作机制
默认按类型匹配,配合@Qualifier可按名称指定。注入过程发生在 Bean 生命周期的属性填充阶段,由AutowiredAnnotationBeanPostProcessor处理。
【深度推导/细节】
核心矛盾:循环依赖的解决
Spring 通过 三级缓存 机制解决构造器注入无法处理的循环依赖问题(仅限单例 Bean 且非构造器注入):
- 一级缓存(singletonObjects):存放完全初始化好的 Bean。
- 二级缓存(earlySingletonObjects):存放早期暴露的 Bean(已实例化但未填充属性)。
- 三级缓存(singletonFactories):存放 Bean 工厂,用于生成早期引用。
解决流程(以 A 依赖 B,B 依赖 A 为例):
- 创建 A 实例,放入三级缓存(工厂可生成 A 的早期引用)。
- 填充 A 的属性时发现需要 B,触发 B 的创建。
- 创建 B 实例,放入三级缓存。
- 填充 B 的属性时发现需要 A,从三级缓存通过工厂获取 A 的早期引用(此时 A 未初始化完),注入给 B。
- B 完成初始化,移入一级缓存。
- A 获得完整的 B 实例,完成初始化,移入一级缓存。
关键限制:构造器注入的循环依赖无法解决,因为实例化阶段就需要完整依赖,此时 Bean 未放入缓存。
【关联/对比】
| 对比维度 | Spring DI | 手动依赖管理 |
|---|---|---|
| 控制权 | 容器控制 | 对象自身控制 |
| 耦合度 | 低(面向接口) | 高(直接实例化) |
| 可测试性 | 高(易替换 Mock) | 低(硬编码依赖) |
| 配置方式 | 注解/XML/Java Config | 代码硬编码 |
与 IoC 容器的关系:
DI 是手段,IoC 是目标。Spring 容器(ApplicationContext)作为 IoC 容器,通过 DI 管理 Bean 的生命周期和依赖关系。
【面试官追问】
-
Spring 如何解决字段注入的循环依赖?与构造器注入有何不同?
(如上所述,字段注入可利用三级缓存;构造器注入在实例化前就需要完整依赖,无法解决。) -
@Autowired 和 @Resource 的区别?
@Autowired是 Spring 注解,默认按类型匹配,支持required=false。@Resource是 JSR-250 注解,默认按名称匹配,名称找不到则回退到类型。
-
为什么推荐使用构造器注入?
- 保证依赖不可变(final 字段)。
- 保证依赖不为 null。
- 避免循环依赖问题(设计上暴露问题)。
- 便于单元测试。
-
Spring 中有哪些注入的“边界情况”或限制?
- 静态字段无法直接注入(需通过 setter 方法配合
@PostConstruct)。 - 同一类型的多个 Bean 需配合
@Qualifier指定。 - 原型 Bean 的循环依赖无法解决(每次获取新实例,缓存机制无效)。
- 静态字段无法直接注入(需通过 setter 方法配合
-
从 Spring 4.3 开始,如果类只有一个构造器,
@Autowired可以省略,为什么?
Spring 认为单一构造器是明确的依赖声明,自动将其视为注入点,简化配置。
【版本差异】
- Spring 2.5:引入
@Autowired注解支持。 - Spring 3.0:强化 Java Config,
@Configuration与@Bean提供纯 Java 配置方式。 - Spring 4.3:单构造器隐式自动注入,推荐构造器注入。
- Spring 5.x:对 Kotlin 原生支持,支持空安全注入。
【最佳实践总结】
- 强制依赖使用构造器注入,可选依赖使用 Setter 注入。
- 避免字段注入,尤其是核心业务 Bean。
- 使用
@Qualifier或@Primary解决同一类型多 Bean 的歧义。 - 通过
@Profile或条件化 Bean(@Conditional)实现环境特定注入。